|
背景“什么是canvas?首先介绍下canvas。前端的同学可能很熟悉,举个很简单的例子:平常用的网页截图、H5游戏、前端动效、可视化图表...,都有canvas 的应用场景。我们看下官方的定义:“canvas是HTML5提供的一种新标签, ie9才开始支持的,canvas是一个矩形区域的画布,可以用JS控制每一个像素在上面绘画。canvas 标签使用 JavaScript 在网页上绘制图像,本身不具备绘图功能。canvas 拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。”看着很简单,其实canvas这个标签的加入,赋予了我们更多创建惊艳的前端效果的能力。但是你知道他也有性能问题?本篇文章就简单谈一谈canvas的性能优化。前言好了现在进入今天的主题: 「canvas 性能优化」, 读完本篇文章你可以学到下面:1. 哪些因素会影响canvas的性能2. canvas优化的几种方式到底是什么因素影响了canvas我们都知道浏览器上渲染动画 每一秒高达60帧,也就是1秒钟内我们完成60次图像绘制, 也就是每一帧图像的绘制时间其实就是(1000/ 60)。?如果在每一帧动画的时间小于 16.7 ms 辣么就会出现卡顿、丢帧。而canvas 其实是一个「指令式绘图系统」, 他通过绘图指令来完成绘图操作。 那么很容易想到两个很关键的因素:1. 「绘制图形的个数」2. 「绘制图形的大小」假设一个图形几毫秒,那么如果绘制10000个图形呢?如果后面其他操作、ui交互、渲染其他图形... 就会导致渲染的时间更长了。canvas 绘制的图像都是一个个小像素点构成的、 你绘制一个半径为5的圆和半径为1000的圆所需要的像素点是不一样的。这里给大家看一张图清晰的明白一个绘制的构成:你图形数量越大需要的像素点就越多,那么片元着色器就要不断的去执行。我写了「两个小demo」 来验证我们的猜想,纸上得来终觉浅,绝知此事要躬行哇!绘制图形的个数主要是绘制100个 和绘制10000个小球作对比。我们先看下代码:const canvas = document.getElementById('canvas') ? ? ?const ctx = canvas.getContext('2d') ? ? ?const WIDTH = canvas.width ? ? ?const HEIGHT = canvas.height ? ? ?function randomColor() { ? ? ? ?return ( ? ? ? ? ?'rgb( ' + ? ? ? ? ?((Math.random() * 255) >> 0) + ? ? ? ? ?',' + ? ? ? ? ?((Math.random() * 255) >> 0) + ? ? ? ? ?',' + ? ? ? ? ?((Math.random() * 255) >> 0) + ? ? ? ? ?' )' ? ? ? ?) ? ? ?} ? ? ?function drawCirle(radius = 10) { ? ? ? ?const x = Math.random() * WIDTH ? ? ? ?const y = Math.random() * HEIGHT ? ? ? ?ctx.fillStyle = randomColor() ? ? ? ?ctx.beginPath() ? ? ? ?ctx.arc(x, y, radius, 0, Math.PI * 2) ? ? ? ?ctx.fill() ? ? ?} ? ? ?function draw(count = 1) { ? ? ? ?for (let i = 0; i const peopleActionCanvas = document.getElementById('peopleActionCanvas');const backgroundCanvas = document.getElementById('backgroundCanvas');function draw(){ ?drawPeopleAction(peopleActionCanvas); ?if (needDrawBackground) { ? ?drawBackground(backgroundCanvas); ?} ?requestAnimationFrame(draw);}一个背景层一个运动层, 在抽象一点,我们什么时候应该去做分层 ,如果画布纯是静态的就没有必要去做分层了,如果当前有静态有动态的,你可以逻辑层放在最上面,然后展示层放在最底下,就可以实现所谓的分层渲染了,但最好保持在3-5个。局部渲染局部渲染其实就是调用canvas 的 clip方法,这个方法很多同学不知道是什么。我们先看下官方文档MDN 对这个方法的使用:“CanvasRenderingContext2D.clip() 是 Canvas 2D API 将当前创建的路径设置为当前剪切路径的方法。”你可以试着思考一下 如何用canvas 画一个1/4圆。const canvas ?= document.getElementById('canvas');const ctx ?= canvas.getContext('2d');ctx.fillStyle = 'red'ctx.arc(100, 100, 75, 0, Math.PI*2, false);//ctx.clip();ctx.fillRect(0, 0, 100,100);这里填充的时候 没有用clip 画面上应该是一个矩形。如图:这时候我把clip注释解开来,你会发现矩形变成了一个半圆。所以clip 这个api 结合 fillRect 填充就是实现填充任意图形路径。我们看下图:这时候就会有同学问,这东西和我们局部渲染有什么关系?大家都知道canvas实现动画,每一帧都会把当前画面的上的东西全部清除然后再重新更新一遍,看下面这个场景:for(let i=0;i标签绑定在一起,这意味着canvas API和DOM是耦合的。而OffscreenCanvas,正如它的名字一样,通过将Canvas移出屏幕来解耦了DOM和canvas API。由于这种解耦,OffscreenCanvas的渲染与DOM完全分离了开来,并且比普通canvas速度提升了一些,而这只是因为两者(Canvas和DOM)之间没有同步。但更重要的是,将两者分离后,canvas将可以在Web Worker中使用,即使在Web Worker中没有DOM。这给canvas提供了更多的可能性。这就离屏canvas 为啥和webworker 这么配的缘故了。如何创建离屏canvas?创建离屏canvas有两种方式:「一种」是通过OffscreenCanvas的构造函数直接创建。比如下面的示例代码:// 离屏canvas const offscreen = new OffscreenCanvas(200, 200);「第二种是使用canvas的transferControlToOffscreen」函数获取一个OffscreenCanvas对象,绘制该OffscreenCanvas对象,同时会绘制canvas对象。比如如下代码:const canvas ?= document.getElementById('canvas');const offscreen = canvas.transferControlToOffscreen();我写了下面这个小demo 验证下到底是不是可靠的 const canvas ?= document.getElementById('canvas'); ?// 离屏canvas ?const offscreen1 = new OffscreenCanvas(200, 200); ?const offscreen2 = canvas.transferControlToOffscreen(); ?console.error(offscreen1,offscreen2, '222') ?打开浏览器的截图如下:离屏canvas我们的猜想是对的。问题又来了,离屏canvas怎么与主线程的canvas通信呢?这时候引用另外一个api 「transferToImageBitmap」通过transferToImageBitmap函数可以从OffscreenCanvas对象的绘制内容创建一个ImageBitmap对象。该对象可以用于到其他canvas的绘制。比如一个常见的使用是:把一个比较耗费时间的绘制放到「web worker」下的OffscreenCanvas对象上进行,绘制完成后,创建一个ImageBitmap对象,并把该对象传递给页面端,在页面端绘制ImageBitmap对象。我们写个小demo测试下:优化前我们画 10000 * 10000 个矩形看看页面的响应和时间,代码如下:const canvas ?= document.getElementById('canvas'); ?const ctx ?= ?canvas.getContext('2d'); ?function draw() { ? ? ?for(let i = 0;i
|
|