• Canvas实战模仿GOOGLE浮动小球效果


    看到基于Canvas动画的Google浮动小球效果,非常炫,决定自己尝试模仿着做一个。

    Demo:http://qs20199.github.io/SuspendingBall/

    这个Demo并不难,包含以下两个部分

    • 物理控制
    • 动画控制

    物理控制

    function Ball(posX,posY,color,radius){
        this.iOriginX=this.iCurX=posX;
        this.iOriginY=this.iCurY=posY;
        this.sColor=color;
        this.iRadius=radius;
        this.iDX=this.iDY=0;
    }
    • OriginX/Y代表小球的原始位置
    • CurX/Y代表小球的实际位置。
    • dx和dy代表了小球在两个方向上的速度

    小球控制有以下逻辑

    • 每一帧小球将移动dx/dy个像素
    //移动
    aBalls[i].iCurX+=aBalls[i].iDX;
    aBalls[i].iCurY+=aBalls[i].iDY;
    • 当小球碰到墙时将反弹
    if((aBalls[i].iCurX<=0+aBalls[i].iRadius)) {
        aBalls[i].iCurX=aBalls[i].iRadius+1;
        aBalls[i].iDX=-aBalls[i].iDX;
    }
    if((aBalls[i].iCurX >=oCanvas.width-aBalls[i].iRadius)) {
        aBalls[i].iCurX=oCanvas.width-aBalls[i].iRadius-1;
        aBalls[i].iDX=-aBalls[i].iDX;
    }
    if(aBalls[i].iCurY<=0+aBalls[i].iRadius) {
        aBalls[i].iCurY=aBalls[i].iRadius+1;
        aBalls[i].iDY=-aBalls[i].iDY;
    }
    if(aBalls[i].iCurY >=oCanvas.height-aBalls[i].iRadius) {
        aBalls[i].iCurY=oCanvas.height-aBalls[i].iRadius-1;
        aBalls[i].iDY=-aBalls[i].iDY;
    }
    • 引力(加速度)控制
      • 原始位置对小球有吸引力
      • 鼠标对小球具有排斥力
      • 存在与移动方向相反的阻力
     //速度控制
    if(oMousePos.x!=""){
            //有鼠标,存在引力和斥力
            var Dis=Math.pow(Math.pow(aBalls[i].iCurX-oMousePos.x,2)+Math.pow(aBalls[i].iCurY-oMousePos.y,2),0.5);
            aBalls[i].iDX+=0.003*(aBalls[i].iOriginX-aBalls[i].iCurX)+20/Math.pow(Dis,1.8)*(aBalls[i].iCurX-oMousePos.x);
            aBalls[i].iDY+=0.003*(aBalls[i].iOriginY-aBalls[i].iCurY)+20/Math.pow(Dis,1.8)*(aBalls[i].iCurY-oMousePos.y);
    }else{
            //无鼠标,只存在引力
            aBalls[i].iDX+=0.003*(aBalls[i].iOriginX-aBalls[i].iCurX);
            aBalls[i].iDY+=0.003*(aBalls[i].iOriginY-aBalls[i].iCurY);
    }
    //限制最大速度
    if(aBalls[i].iDX>MaxSpeed) aBalls[i].iDX=MaxSpeed;
    if(aBalls[i].iDX<-MaxSpeed) aBalls[i].iDX=-MaxSpeed;
    if(aBalls[i].iDY>MaxSpeed) aBalls[i].iDY=MaxSpeed;
    if(aBalls[i].iDY<-MaxSpeed) aBalls[i].iDY=-MaxSpeed;
    //阻力
    aBalls[i].iDX*=0.98;
    aBalls[i].iDY*=0.98;

    以上物理控制流程包含在了updateBalls()函数里,而这个函数并不实际进行动画,只是对象属性。

    实际上计算公式并没有硬性规定,公式不同自然会有不同的运动效果,大家可以自己研究。


    动画控制

    动画采用requestAnimationFrame,而没有采用定时器(实测前者的流畅度将远超后者)。关于requestAnimationFrame,网上已经有了很详尽的资料,在此不在赘述。

    function draw() {
        updateBalls();
        oContext.clearRect(0,0,oCanvas.width,oCanvas.height);
        oContext.fillText("by QS",800,260);
        for(var i=aBalls.length-1;i>=0;i--){
            oContext.beginPath();
            oContext.arc(aBalls[i].iCurX,aBalls[i].iCurY,aBalls[i].iRadius,0,2*Math.PI);
            oContext.fillStyle=aBalls[i].sColor;
            oContext.fill();
        }
        requestAnimFrame(draw);
    };

    注意遍历每个球的时候,首先要beginPath(),否则球将会连接在一起。


    以上是这个demo的核心部分。

    写的过程中遇到了以下几个问题

    撞墙检测

    由于位置的变化不是连续的(即dx/dy会大于1),因此总有可能让小球直接穿过墙壁。试了几种算法,都不能很好的解决这个问题,后来读了Google小球的源码,发现他的处理方式是:只要到了区域范围以外,就将位置坐标强制设为临界值(而非在基础上加dx/dy)。这么说可能表述不清楚,看代码就清楚了:

    if((aBalls[i].iCurX<=0+aBalls[i].iRadius)) {
        aBalls[i].iCurX=aBalls[i].iRadius+1;
        aBalls[i].iDX=-aBalls[i].iDX;
    }

    阻力

    一开始没有考虑到阻力,于是小球最后在原始位置附近徘徊,后来想起高中学的动量守恒,就想起来阻力这回事了。

    aBalls[i].iDX*=0.98;
    aBalls[i].iDY*=0.98;
  • 相关阅读:
    Druid源码解析(六):PreparedStatementPool源码及使用场景分析 lcl
    PyScript:让Python在HTML中运行
    Java 16 新特性:record类
    Java 17 新特性:switch的模式匹配(Preview)
    基于阿里云 ASK 的 Istio 微服务应用部署初探
    Vue快速入门
    架构设计分析
    springbatch相关sql创建语句 mysql
    mysql多表关联时可能出错的地方,如搜索出的记录数据变少了。
    https://blog.csdn.net/hualusiyu/article/details/86498452?spm=1001.2014.3001.5502
  • 原文地址:https://www.cnblogs.com/qs20199/p/4452271.html
Copyright © 2020-2023  润新知