canvas2d动画、优化

一、动画

动画通过每帧放出不同的画面来实现,而每帧的需要的操作如下:

  1. 清空上一帧绘制的内容。
  2. 如果需要保存当前canvas状态(笔刷样式、变形)的话就将其保存一下。
  3. 绘制这一帧需要画的内容。
  4. 如果之后需要恢复之前保存的状态的话就恢复一下。

由于动画需要按时切换显示的内容,所以会使用到和时间相关的函数:

  • setInterval(callback, delay)
  • setTimeout(callback, delay)
  • requestAnimationFrame(callback)

由于setTimeoutsetInterval有可能被其他语句耽误,通常使用requestAnimationFrame方法。

本博客banner的动画也是使用这个方法制作的,大致步骤如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let length // 精灵图长度
let ctx // canvas.getContext('2d')
ctx.renderIndex = 0
let img // 精灵图
function render () {
while (ctx.renderIndex < length) {
// 清空上一帧内容
ctx.clearRect(0, 0, canvas.width, canvas.height)
// 精灵图内在竖直方向存放了帧动画,通过修改drawImage的绘图位置便可以简单绘制该帧
ctx.drawImage(img, 0, ctx.renderIndex * canvas.height)
ctx.renderIndex = ctx.renderIndex < length ? ctx.renderIndex + 1 : 0
}
requestAnimationFrame(render)
}
requestAnimationFrame(render)

商业中会使用Adobe Animate或Spine来绘制并导出canvas格式的动画,然后只要导入到页面中即可展示动画。

二、优化

  1. 双缓冲

    在游戏内会经常使用双缓冲来提高性能,简单地说就是预先在另外一个离屏的canvas上绘制好大量复杂内容,然后在需要的时候直接把离屏canvas渲染到当前canvas中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let canvas // 当前canvas
    ctx = canvas.getContext('2d')
    let _canvas // 离屏canvas
    _canvas = document.createElement('canvas')
    _canvas.width = canvas.width
    _canvas.height = canvas.height
    const _ctx = _canvas.getContext('2d')

    ctx.drawImage(_canvas, x, y, w, h)
  2. 避免浮点数坐标

    1
    2
    // 当画一个没有整数坐标点的对象时会发生子像素渲染,浏览器为了达到抗锯齿的效果会做额外的运算。使用前请对坐标取整。
    ctx.drawImage(myImage, 0.3, 0.5)
  3. 在离屏canvas中缓存图片的不同尺寸,而不要用drawImage()去缩放。

  4. 对于具有不相关动画效果的内容,用多个canvas来展示:

    比如很多游戏的角色游戏层、背景图、UI这三个内容分别有完全无关的动画效果,这时候可以配置三个canvas,只在这个图层需要的时候重绘。对于静态背景,甚至可以直接使用img标签或background代替。

  5. 用CSS的transform缩放画布:

    transform使用GPU,因此速度更快。 最好的情况是不直接缩放画布,或者具有较小的画布并按比例放大,而不是较大的画布并按比例缩小。

  6. 关闭透明度:

    如果不需要背景透明的画布,那么当使用 canvas.getContext时把 alpha 选项设置为 false 。这个选项可以帮助浏览器进行内部优化。

    1
    const ctx = canvas.getContext('2d', { alpha: false })
  7. 其他: