Canvas 动画原理

动画的基本步骤

你可以通过以下的步骤来画出一帧:

  1. 清空 canvas 除非接下来要画的内容会完全充满 canvas(例如背景图),否则你需要清空所有。最简单的做法就是用 clearRect 方法。

  2. 保存 canvas 状态 如果你要改变一些会改变 canvas 状态的设置(样式,变形之类的),又要在每画一帧之时都是原始状态的话,你需要先保存一下。

  3. 绘制动画图形(animated shapes) 这一步才是重绘动画帧。

  4. 恢复 canvas 状态 如果已经保存了 canvas 的状态,可以先恢复它,然后重绘下一帧。

更新画布

为了执行动画,需要 定时执行重绘 的方法,可以通过下面三个函数实现页面渲染的重绘(推荐使用 requestAnimationFrame)。

  • setInterval(function, delay)
    当设定好间隔时间后,function 会定期执行。

  • setTimeout(function, delay)
    在设定好的时间之后执行函数

  • requestAnimationFrame(callback)
    告诉浏览器你希望执行一个动画,并在重绘之前,请求浏览器执行一个特定的函数来更新动画。

如果你并不需要与用户互动,你可以使用 setInterval() 方法,它就可以定期执行指定代码。

如果我们需要做一个游戏,我们可以使用键盘或者鼠标事件配合上 setTimeout() 方法来实现。通过设置事件监听,我们可以捕捉用户的交互,并执行相应的动作。

更推荐使用 requestAnimationFrame()

setInterval()

是一个定时器,用于定时执行一段代码,通过这个特定可以定时调用会canvas的重绘,实现动画效果。

setInterval(function, milliseconds) 定时执行并不是严格的间隔固定时间执行。因为 milliseconds 会包含 function 执行的耗时。
对于先要实现 固定间隔 执行一段代码的效果,推荐使用 setTimeout() 函数。

例子

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script src="js/CanvasRecorder.js"></script>
        <style>
            #c{
                width: 400px;
                height: 300px;
            }
        </style>
    </head>
    <body>
        <canvas id="c" ></canvas>
        <script>
            var c=document.getElementById("c");
            c.width=c.clientWidth*window.devicePixelRatio;
            c.height=c.clientHeight*window.devicePixelRatio; 

            // console.log("画布宽度高度:",c.width,c.height)
            // c.style.imageSmoothingEnabled = false;
            // c.style.webkitImageSmoothingEnabled = false;
            // c.style.mozImageSmoothingEnabled = false;

            var ctx=c.getContext("2d");

            let x = 20
            let speed = 1
            function draw(){
                //1. 先把画布清空
                ctx.clearRect(0, 0, c.width, c.height);

                // 设置背景色,本质是画一个与canvas相同大小的矩形
                // 设置颜色
                ctx.fillStyle = 'bisque';
                // 填充矩形
                ctx.fillRect(0, 0, c.width, c.height);

                //设置颜色
                ctx.fillStyle = "green";
                // 设置字体
                ctx.font="40px Georgia";
                // 设置文字
                ctx.fillText("Hello World!",80,150);

                //设置颜色
                ctx.fillStyle = "blue";
                // 设置字体
                ctx.font="30px 微软雅黑";
                ctx.fillText("你好,世界!",80,200);

                // 画矩形
                ctx.fillStyle = "red";
                ctx.fillRect(x, 50, 50, 50);

                // 让矩形框左右移动
                x+=speed
                if(x>c.width - 50 - 20||x<20){
                    speed = -speed;
                }
            }
            // 一秒刷新60
            setInterval(function(){
                draw()
            },1000/60)

        </script>
    </body>
</html>

setTimeout()延迟执行

setTimeout() 用于延迟指定的时间执行一次函数,通过 setTimeout 定时执行的回调中嵌套调用 setTimeout() 可以实现,固定间隔的执行一段代码。
具体使用和 setInterval 类似,不再赘述

setTimeout和setInterval 缺点

setTimeout和setInterval指定的回调函数,必须等到本轮事件循环的所有同步任务都执行完,才会开始执行。
由于前面的任务到底需要多少时间执行完,是不确定的,所以没有办法保证,setTimeout和setInterval指定的任务,一定会按照预定时间执行。

requestAnimationFrame

setTimeout 和 setInterval 有可能无法按指定时间运行,同时也无法保证在正确的页面重绘时执行,影响浏览器的绘制性能等,所以通常在动画中,推荐使用requestAnimationFrame()。

优点

requestAnimationFrame 是html5专门请求动画帧的API。由系统决定回调函数的执行(根据显示器刷新频率等多种因素),不会引起丢帧和卡顿。
requestAnimationFrame 要想循环执行,需要在回调中再次调用 requestAnimationFrame

window.requestAnimationFrame() 告诉浏览器 —— 你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。
该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

例子

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script src="js/CanvasRecorder.js"></script>
        <style>
            #c{
                width: 400px;
                height: 300px;
            }
        </style>
    </head>
    <body>
        <canvas id="c" ></canvas>
        <script>

            var c=document.getElementById("c");
            c.width=c.clientWidth*window.devicePixelRatio;
            c.height=c.clientHeight*window.devicePixelRatio; 

            var ctx=c.getContext("2d");

            let x = 20
            let speed = 1
            function draw(){
                //1. 先把画布清空
                ctx.clearRect(0, 0, c.width, c.height);

                // 设置背景色,本质是画一个与canvas相同大小的矩形
                // 设置颜色
                ctx.fillStyle = 'bisque';
                // 填充矩形
                ctx.fillRect(0, 0, c.width, c.height);

                //设置颜色
                ctx.fillStyle = "green";
                // 设置字体
                ctx.font="40px Georgia";
                // 设置文字
                ctx.fillText("Hello World!",80,150);

                //设置颜色
                ctx.fillStyle = "blue";
                // 设置字体
                ctx.font="30px 微软雅黑";
                ctx.fillText("你好,世界!",80,200);

                // 画矩形
                ctx.fillStyle = "red";
                ctx.fillRect(x, 50, 50, 50);

                // 让矩形框左右移动
                x+=speed
                if(x>c.width - 50 - 20||x<20){
                    speed = -speed;
                }
                // 递归调用 draw() 函数更新画布
                window.requestAnimationFrame(draw)
            }

            // 调用 draw() 函数更新画布
            window.requestAnimationFrame(draw)
        </script>
    </body>
</html>

参考:
https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Basic_animations
https://juejin.cn/post/7002131643326529572


原文出处:/show_1IX8QiEkcrID.html