canvas动画之绘制思路
前面学习了canvas的基础知识,现在是时候学习如何绘制酷炫的动画了。好吧,这里先整理下绘制动画的基本思路。
参考
相关概念
先理清动画的基本概念,然后在了解绘制动画的一些思路。
动画
下面先来一段动画的概念,来源维基百科
动画是指由许多帧静止的画面,以一定的速度(如每秒16张)连续播放时,肉眼因视觉残象产生错觉,而误以为画面活动的作品。为了得到活动的画面,每个画面之间都会有细微的改变。
为了实现动画,我们只需要准备静止的画面,然后以某种策略,连续播放画面即可。
人的眼睛能感知的最大帧数是每秒60帧,换句话说,每一帧我们可以使用的时间是1000/60=16.66
ms。当然对于某些游戏场景而言,二三十帧可能就够了。
帧
通过调用canvas的API,我们可以绘制出各种图形和图像
在canvas中,图像一旦绘制,就无法更改了,我们能做的就是对其进行重绘。
可以通过以下的步骤来画出一帧:
- 清空 canvas,最简单的做法就是用 clearRect 方法。
- 保存 canvas 状态,可以用save和restore方法
- 绘制动画图形,调用canvas绘制接口进行绘制,绘制出来的结果即为当前帧能够看见的图像
接着只需要定时重复步骤1-3,然后重绘下一帧,周而复始,就可以实现连续的动画了。之前我们已经学习了如何绘制帧,现在让我们来看看定时器。
定时器
window对象提供了两个定时器的接口 setTimeout
通过递归,可以实现setTimeout
定时循环调用
let loop = ()=>{
console.log('loop')
setTimeout(() => {
loop()
}, 1000);
}
loop()
setInterval
setInterval
的调用更加简单直观一些
setInterval(()=>{
console.log('loop in setInterval')
},1000)
requestAnimationFrame 该方法接收一个回调函数,这个回调函数会在浏览器重绘之前被调用,回调的次数通常是每秒60次,但大多数浏览器通常匹配 W3C 所建议的刷新频率,更新频率是浏览器自动控制的,因此不需要我们手动设置时间间隔。
这个接口比传统的定时器效率更高,参考MDN文档
一个简单的兼容形式
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
当然这里还需要考虑cancelAnimationFrame等形式,这里有一份参考代码。
下面使用requestAnimFrame
实现一个最简单的方块移动的动画
let width = 50,
height = 50,
x = 0,
y = 0,
speed = 1;
function update(){
ctx.clearRect(0, 0, canvas.width, canvas.height);
x += speed;
ctx.fillRect(x, y, width, height);
requestAnimationFrame(update);
}
requestAnimationFrame(update);
如果我们在moveBall
中绘制更精致的帧,就可以看见酷炫的动画啦~
常见动画的实现
上面剖析了动画的概念,即连续播放的帧,而每帧的过渡也大有学问。大部分的动画内容都可以看作是位移、缩放、旋转的运动渐变。而这些概念与CSS动画基本是互通的~
以位移举例,在上面的例子中实际上就是位移动画的展示,位移动画的实现就是在后一帧更新前一帧的元素位置即可
// 水平方向平移,其中vx表示较前一帧水平位置的变化,可以通过控制vx的变化来实现加速度平移
x += vx
// 竖直方向平移,同上
y += vy
实际上关于动画的变化速度,可以利用许多数学函数来实现,最著名的就是tween.js,这个库实现了一个链式调用的缓动动画,如果我们只需要里面的数学计算公式,可以试试张鑫旭提供的[精简版],相关使用可以参考(https://github.com/zhangxinxu/Tween/blob/master/tween.js)
以线性速度为例
/*
* t: current time(当前时间);
* b: beginning value(初始值);
* c: change in value(变化量);
* d: duration(持续时间)。
*/
Tween.Linear = function (t, b, c, d) {
return c * t / d + b;
},
如果需要实现一个线性运动的动画,实际上上面函数的b,c,d都是在设定之初就已经可以决定的,只要输入了当前时间,就可以返回对应时间点的位置值,然后,让canvas根据这个位置绘制出对应的图形即可。
那个,如何把定时器内的当前时间和Tween结合起来呢?通过一个计时变量维持当前时间的引用即可。
var time = 0
setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ball.draw();
time++;
if (time <= duration) {
// 继续运动
ball.y = Tween.Linear(time, begin, end, duration);
requestAnimationFrame(update);
} else {
// 动画结束
}
}, 1000);
如果在requestAnimationFrame中,计时器并不是以每秒一次的频率触发,因此每帧中time增加的幅度也不一样,这里需要注意下
使用面向对象的思维来绘制图形
在刚开始学习编程的时候,面对C++的面向对象,我一脸懵逼,依稀还记得猫叫狗叫动物叫....
扯远了,从上面可以得知,我们需要在每一帧中完成整个画布的绘制,然后在下一帧中清空画布,重新绘制。如果画面比较复杂,则我们的绘制函数会变得十分庞大,每个图 形都有自己的绘制参数,这样维护起来就十分困难,因此,通过对象去管理每一个需要绘制的图形,是一个不错的选择。
我们来改造一下上面的代码
let sprite = {
width: 50,
height: 50,
x: 0,
y: 0,
speed: 1,
draw(){
ctx.fillRect(this.x, this.y, this.width, this.height);
},
update(){
this.x += this.speed;
this.draw()
}
function update(){
ctx.clearRect(0, 0, canvas.width, canvas.height);
sprite.update()
requestAnimationFrame(update);
}
requestAnimationFrame(update);
还可以通过sprite这个对象来保存它的其他绘制信息,比如颜色、速度、边界逻辑等,即使我们要再添加一个元素到画布上,也不会干扰之前的元素了。
回到这个对象身上,
- 如果需要控制移动速度,只需要改变speed值的大小;
- 如果需要添加加速度,则在每一帧中去改变speed
- 如果需要添加边界判断,则在绘制时判断x和y的大小
- 如果需要添加用户交互,只需要监听事件,然后在事件回调中去设置对象对应的属性
通过对象去管理需要绘制的属性,可以更直观更方便,为了练手,我整理了一个canvas练习项目(https://github.com/tangxiangmin/amazing-canvas),目前包括
- 打砖块,只实现了基本的游戏逻辑,后面有空再继续完善。
- 下雪花效果
不管怎么样,还是要多练习才行。
性能优化
如果动画比较复杂,我们就不得不考虑性能优化的问题,尤其是在移动端,性能问题表现得更加显著。下面是在整理这篇博客时了解到性能优化方面的一些知识点,暂做记录。参考
局部重绘
在上面的代码中,绘制每一帧,我们都清空了整个画布,这意味着在上一帧没有改变的元素,我们在这一帧仍旧需要重新绘制,这会导致频繁调用canvas绘制接口,带来性能的消耗。如果按需进行局部重绘,绘制面积要小多,因此显著提高绘制效率。
关于局部重绘,这里有一篇文章:HTML5 canvas实现脏区重绘,不妨移步阅读。
硬件加速
现代浏览器大都可以利用GPU来加速页面渲染,可以通过在Chrome浏览器地址栏输入about:gpu
来检测浏览器是否开启了canvas硬件加速。
硬件加速在移动端尤其有用,因为它可以有效的减少资源的利用。使用GPU可能会导致严重的性能问题,因为它增加了内存的使用,而且它会减少移动端设备的电池寿命 GPU是相对于CPU的一个概念,由于在现代的计算机中图形的处理变得越来越重要,需要一个专门的图形的核心处理器。GPU比CPU更擅长处理图形绘制。
关于gpu硬件加速,这里有一篇不错的文章。
小结
在研究动画绘制的时候,发现了一个貌似有点酷炫的软件:Animate CC,参考
讲道理,这种可视化的动画编辑对于开发和调试要方便得多,但是对于动画原理、性能优化等,还是需要扎实的底层基础才行。因此,学习canvas动画绘制怎么看都是一件很划算的事情。
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。