canvas动画实战与性能优化

插播一篇关于 canvas 动画及性能优化的文章,为我们可以更好的进入到 webgl 的世界奠定基础。

本篇文章的内容可能会稍难理解,还希望大家有问题及时提出。闲话我们就不多说了,开始今天的正题吧。

1. 动画实战

首先介绍一下我们要实现的动画内容: 夜空中的流星源码

今天就来跟大家详细分享一下如何进行编写 canvas 动画以及如何进行优化。

1.1 搭建页面

canvas的页面组成是非常简单的。如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>canvas动画和性能优化实战</title>
  <!-- 引入页面的样式 -->
  <link rel="stylesheet" href="./style/index.css">
</head>
<body>
  <canvas id="shooting-star">您的浏览器不支持canvas标签,请升级版本或选择其他浏览器</canvas>
</body>
</html>
<!-- 引入页面的功能 -->
<script src="./js/index.js"></script>

创建一个 html 文件,并且引入 css 和 js 文件。

这里有一个问题,之前我们跟大家分享的时候说,canvas标签的宽和高要在html中进行设置。但是为了适配我们的屏幕,就得用js来设置画布的宽和高。

1.2 具体实现

1. 首先创建一个流星的类。并添加一个启动的方法
class ShootingStar{
  // 构造方法
  constructor() {}
  // 启动的方法
  start() {}
}
// 实例化一个对象,并启动
new ShootingStar().start();
2. 获取到 canvas 对象和绘图上下文,并设置 canvas 的宽和高

注意:这里设置宽高是因为画布会出现模糊的现象。

// 构造方法
constructor() {
    // 获取canvas对象
  this.ctx = document.getElementById('id名称');
  // 获取绘图上下文
  this.c = this.ctx.getContext('2d');
  // 获取当前页面的宽高
  this.maxW = document.body.offsetWidth;
  this.maxH = document.body.offsetHeight;
  
  // 定义一个数组,用来存放夜空中星,之后的开发中会用到
  this.skyList = [];
}
// 初始化画布的宽和高
initCanvasSize() {
  this.ctx.width = this.maxW;
  this.ctx.height = this.maxH;
}
// 在起始方法中调用初始化函数
start() {
  this.initCanvasSize();
}
3. 绘制背景

这里我们没有引入图片,是模拟了一个星空的效果,略微简陋。

// 绘制背景
drawBackground() {
  // 夜空中星的数量
  const maxCount = 500;
  // 创建一个黑色的纯色背景
  this.c.fillRect(0, 0, this.ctx.width, this.ctx.height);

  // 创建随机坐标。在当前页面内随机
  for (let i = 0; i < maxCount; i++) {
    const r = ~~(Math.random() * 3);
    const x = ~~(Math.random() * this.maxW);
    const y = ~~(Math.random() * this.maxH);

    // 将随机坐标推入数组中
    this.skyList.push({
      x,y,r
    })
  }
}
4. 开启流星模式
// 初始化背景,并重新开始绘制流星
initBackground() {
  this.c.beginPath();
  this.c.fillStyle = 'rgba(0,0,0,0.2)';
  // 清空当前画布
  this.c.rect(0,0,this.maxW, this.maxH);
  this.c.fill();
  // 重新绘制背景
  this.drawStarList();
  // 重新开始绘制流星
  this.startShootingStar();
}
// 绘制流星
drawStar(x, y) {
  this.drawStarList()
  this.c.beginPath();
  this.c.fillStyle = 'white';
  this.c.arc(x,y,2,0,Math.PI * 2);
  this.c.fill();
}
// 添加拖尾效果
drawCover() {
  this.c.beginPath();
  this.c.fillStyle = 'rgba(0,0,0,0.06)';
  this.c.rect(0,0,this.ctx.width,this.ctx.height);
  this.c.fill();
}
// 开启流星模式
startShootingStar() {
  // 设置x和y的运动速度
  let x = ~~(Math.random() * this.maxW);
  let y = 4;
  // 设置流星的颜色。
  this.c.fillStyle = 'darkorange';
  // 获取一个流星滑落的最大距离。到这个距离之后消失
  const clearY = Math.random() * this.maxH;

  // 绘制函数。
  const draw = () => {
    x -= 1;
    y += 4;

    // 如果当前滑落的距离大于最大距离,初始化当前背景。
    if (y >= clearY) {
      this.initBackground();
      return;
    }

        // 绘制流星,传入当前的 x、y 坐标
    this.drawStar(x, y);
    // 此函数用来使流星有拖尾效果。
    this.drawCover();
    // 使用此函数实现动画效果
    requestAnimationFrame(draw);
  }
  draw()
}

到这里动画实战部分的内容就分享完了,有兴趣的同学可以查看下源码,也可以试着自己实现以下。

2. 性能优化

2.1 使用计算代替频繁的渲染

绘制过程中,通常会使用计算来代替频繁的画布渲染。原理类似于 dom回流。因为 canvas 也是属于 dom 的部分,过多的操作会影响性能。当然,如果你使用一个特别消耗实现的算法的话就另当别论了。

2.2 使用 requestAnimationFrame

很多情况下,我们都习惯于使用 setInterval、setTimeout 来实现页面中的动画。也有很多小伙伴发现这种实现会出现丢帧的现象。原因有二:

  • setInterval、setTimeout 依赖于浏览器的异步循环,因而我们设定的动画执行时间可能并不是真正的动画执行时间,可能这个时候的 js引擎 正在执行其他代码,所以就会出现丢帧的情况。
  • 刷新频率收屏幕分辨率和屏幕尺寸影响。不同设备的屏幕刷新率可能不同。

针对以上两点内容,我们使用 requestAnimationFrame 来优化动画实现。相对于前者,它有两点明显的优势

  • 由系统来决定回调的执行时机,在执行时浏览器会优化方法的调用。
  • 按帧进行重绘,通过 requestAnimationFrame 调用回调函数引起的页面重绘或回流的时间间 隔和显示器的刷新时间间隔相同。所以 requestAnimationFrame 不需要像 setTimeout 那样传递时间间隔,而是浏览器通过系统获取并使用显示器刷新频率

2.3 离屏渲染

离屏渲染可以理解为创建一个备用canvas但是不显示到页面上,执行预渲染操作。此项操作用到了drawImage 这个方法,此方法第一个参数可以接受一个图片对象或者是一个canvas 对象

具体实现:

将需要操作的内容在离屏的canvas上先处理好,之后再使用drawImage方法放入到显示层。

2.4 分层画布

也是使用多个canvas的方式,将静态的内容和需要频繁计算的内容分开渲染,越复杂的场景越适合使用此方法。具体实现如下

示意图:

优化.png

通过这种方式可以将我们的需求拆分为多个模块。将需要频繁绘制的内容拆分出来,从而减少性能的消耗。

性能优化方式主要是一些日常的注意点和拆分方式,并不是万能的。

canvas的动画实现中,算法也占据了很大一部分,比如canvas中的粒子操作,动辄就是成千上万的像素点,算法的使用不当可能会带来很大的问题。

好了,今天的分享就到这里了,临时插播的一条内容。接下来会分享关于 webgl 的内容,版兴趣的同学不要错过哟。Bye~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容