网站首页 » 前端开发 » HTML5 » Canvas 动画之多对象自由落体运动
上一篇:
下一篇:

Canvas 动画之多对象自由落体运动

在上一篇文章中分享了《Canvas 实现多对象运动动画》,此次分享的是在它的基础上进行代码修改,实现多个小球作自由落体运动的动画效果。一步一个脚印,记录下来,在自己回过头来看时能清楚地看到 Canavs 之路首阻且长的辛酸史。

HTML 代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Canvas 多对象自由落体运动动画-云库前端</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        html,
        body {
            width: 100%;
            height: 100%;
        }
        canvas {
            display: block;
            background: #000;
        }
    </style>
</head>
<body>
    <script src="index.js"></script>
    <script>
    var iCircle = new Circle();
    </script>
</body>
</html>

下面我们来看看关键代码。关键代码都有注解,祝旅途愉快。

JavaScript 代码
/*
* @Author: 朝夕熊
* @Date:   2017-11-03 18:27:45
* @Last Modified by:   朝夕熊
* @Last Modified time: 2017-11-03 20:48:11
*/
function Circle() {
    this.init();
    this.generalRandomParam();
    this.drawCircles();
    this.ballAnimate();
}
// 初始化
Circle.prototype.init = function(){
    //父元素宽高
    this.stateW = document.body.offsetWidth;
    this.stateH = document.body.offsetHeight;
    this.iCanvas = document.createElement("canvas");
    // 设置Canvas 与父元素同宽高
    this.iCanvasW = this.iCanvas.width = this.stateW;
    this.iCanvasH = this.iCanvas.height = this.stateH;
    // 获取 2d 绘画环境
    this.ctx = this.iCanvas.getContext("2d");
    // 插入到 body 元素中
    document.body.appendChild(this.iCanvas);
    // 随机生成圆的数量
    this.ballNumber = ramdomNumber(50, 100);
    // 保存所有小球的数组
    this.balls = [];
    this.speed = 0;
    // 保存动画中最后一个停止运动的小球
    this.ballLast = {};
    this.animte = null;
}

// 渲染出所有圆
Circle.prototype.drawCircles = function () {
    for(var i=0;i<this.ballNumber;i++){
        this.renderBall(this.balls[0]);
    }
}

// 随机生成每个圆的相关参数
Circle.prototype.generalRandomParam = function(){
    for(var i=0;i<this.ballNumber;i++){
        var ball = {};
        ball.a = ramdomNumber(0.3, 1); // 随机生成一个加速度
        ball.size = ramdomNumber(3, 50); // 随机生成圆半径
        // 随机生成圆心 x 坐标
        ball.x = ramdomNumber(0+ball.size, this.iCanvasW-ball.size);
        ball.y = 0;
        ball.speed = 1;
        this.balls.push(ball);
    }

    // 根据小球的加速度来重排生成的数组
    this.balls.sort(function(preItem,nextItem){
        if(preItem.a>nextItem.a){
            return 1;
        }else if(preItem.a<nextItem.a){
            return -1;
        }else{
            return 0;
        }
    });

    // 取已排好序的数组中的第一个,也就是动画中最后一个停止运动的小球,为取消动画帧的提供关键条件
    this.ballLast = this.balls[0];
}

// 改变圆的位置
Circle.prototype.changeposition = function(){
    for(var i=0;i<this.ballNumber;i++){
        this.balls[i].speed += this.balls[i].a;
        // this.balls[i].x += this.balls[i].speed;
        this.balls[i].y += this.balls[i].speed;
    }
}

// 画圆
Circle.prototype.renderBall = function(ball){
    this.ctx.fillStyle = "#fff";
    this.ctx.beginPath(); // 这个一定要加
    this.ctx.arc(ball.x, ball.y, ball.size, 0, 2 * Math.PI);
    this.ctx.closePath(); // 这个一定要加
    this.ctx.fill();

}

// 开始动画
Circle.prototype.ballAnimate = function(){
    var This = this;
    var prevSpeed = 0;
    var canMoveDis = 0; // 保存小球到顶点时 小球 Y 轴坐标离画面底部的距离 
    var animateFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
    (function move(){
        animte = animateFrame(move);
        This.ctx.clearRect(0, 0, This.iCanvasW, This.iCanvasH);
        This.changeposition();
        for(var i=0;i<This.ballNumber;i++){
           if(This.balls[i].y>This.iCanvasH-This.balls[i].size){
                This.balls[i].y = This.iCanvasH-This.balls[i].size;
                This.balls[i].speed = -This.balls[i].speed*0.8;
           }
           This.renderBall(This.balls[i]);
        }
        canMoveDis = This.iCanvasH-This.ballLast.size-This.ballLast.y;
        if(prevSpeed<0 && This.ballLast.speed>0 && canMoveDis<1){
            // 如果可自由落下的距离小于 1 ,则取消动画帧
             cancelAnimationFrame(animte);
        }
        // 保存此刻小球的速度,用于与下一次速度比较,并配合 canMoveDis 来判断是否需要取消动画帧
        prevSpeed = This.ballLast.speed;
    })();
}

// 生成一个随机数
function ramdomNumber(min, max) {
    return Math.random() * (max - min) + min;
}

在 JavaScript 代码中,代码本身没什么意思,重要的是从代码中反推效果的实现思路。不管是什么语言,语言背后的逻辑,才是精髓。

在做这个自由落体运动效果的时候,遇到一个比较大的困难就是,如何在众多的小球中判断动画什么时候该结束,因为当所有的小球都结束运动后,我们肯定不想让动画帧(window.requestAnimationFrame)还一直地执行,我们应该结束它。本文通过一个比较笨的方法来实现这个时机的判断,大至思路:在第次动画帧中通过最后一个运动的小球(ballLast)回弹到最高点时判断它与画布底部的距离(canMoveDis),如果小于1 则认为所有的小球都已经停止运动了,此时取消动画帧,不再执行动画,腾出资源。

本例子中得注意 ballAnimate 方法中的 this 指向。

在线DEMO:http://yunkus.com/demo/canvas/canvas-multiple-object-free-falling-body-animation/

  • 微信扫一扫,赏我

  • 支付宝扫一扫,赏我

声明

原创文章,不经本站同意,不得以任何形式转载,如有不便,请多多包涵!

本文永久链接:http://yunkus.com/canvas-multiple-object-free-falling-body-animation/

发表评论

电子邮件地址不会被公开。 必填项已用*标注

评论 END