【前端冷知识】Canvas 滤镜的性能优化

二叶草 2020年2月5日09:18:48优化评论阅读模式

 

最近几天没有及时更新,是因为这几天在忙一个项目mesh.js,这个项目是一个基于Canvas2D和WebGL的跨平台图形系统,提供底层的高性能API,同时也将是未来新版SpriteJS的底层渲染引擎。

这个项目目前主要API已经全部实现,在整理文档、撰写测试的收尾阶段,有兴趣的同学可以关注它。今天主要讲我们在开发过程中有针对地做的性能优化中的一个小点:Canvas的滤镜优化。

我们知道Canvas是支持滤镜的,几乎支持所有与CSS3滤镜一致的滤镜效果,包括:

  • url
  • blur
  • brightness
  • contrast
  • drop-shadow
  • grayscale
  • hue-rotate
  • invert
  • opacity
  • saturate
  • sepia

要使用这些滤镜也很简单,直接给context设置filter属性即可:

const canvas = document.querySelector('canvas');

const context = canvas.getContext('2d');

 

context.filter = 'blur(5px)';

context.fillStyle = 'blue';

context.beginPath();

context.arc(80, 80, 50, 0, Math.PI * 2);

context.fill();


这样就绘制出一个带有模糊滤镜的圆形。

【前端冷知识】Canvas 滤镜的性能优化

但是,滤镜是一种比较消耗性能的操作,尤其是类似于blur,drop-shadow这样的滤镜,更是消耗性能。如果我们要绘制多个图形,应用同样的blur滤镜,就会明显感到性能的消耗。

我们可以通过例子对比一下:

假设我们要在画布上绘制并刷新200个随机的蓝色小圆点,代码也比较简单:

const canvas = document.querySelector('canvas');

const context = canvas.getContext('2d');

 

const count = 200;

 

// context.filter = 'blur(5px)';

context.fillStyle = 'blue';

 

function render(context) {

context.clearRect(0, 0, 512, 512);

for(let i = 0; i < count; i++) {

const pos = [Math.random() * 512, Math.random() * 512];

context.beginPath();

context.arc(...pos, 10, 0, Math.PI * 2);

context.fill();

}

}

 

render(context);

 

requestAnimationFrame(function update() {

render(context);

requestAnimationFrame(update);

});


上面的代码是不带滤镜的效果,可以看到在普通的笔记本电脑的浏览器上,帧率也能轻松达到60fps。

【前端冷知识】Canvas 滤镜的性能优化

如果我们加一个blur滤镜,把上面代码注释掉的代码的注释去掉让它生效

context.filter = 'blur(5px)';


【前端冷知识】Canvas 滤镜的性能优化

看到帧率在我的电脑上已经下降到15帧一下,而且电脑风扇猛转。

所以如果我们要绘制多个相同滤镜的图形,普通的设置滤镜的方法,会导致性能开销特别大。那么除了谨慎使用滤镜外,有没有什么办法优化性能呢?

实际上,对这个case,因为连续绘制相同滤镜的图形,我们可以考虑将滤镜绘制合并起来,具体做法是:

  1. 先绘制不带滤镜的图片到一个干净的缓冲canvas上
  2. 将滤镜设置到我们要绘制的目标canvas上
  3. 用drawImage将图片从缓冲canvas绘制到目标canvas上
  4. 将滤镜设置从目标canvas上取消(以继续绘制其他内容,如果有的话)

我们看一下修改后的代码:

const canvas = document.querySelector('canvas');

const context = canvas.getContext('2d');

 

const count = 200;

 

const tempCanvas = new OffscreenCanvas(canvas.width, canvas.height);

const tempContext = tempCanvas.getContext('2d');

 

tempContext.fillStyle = 'blue';

 

function render(context, tempContext) {

tempContext.clearRect(0, 0, 512, 512);

for(let i = 0; i < count; i++) {

const pos = [Math.random() * 512, Math.random() * 512];

tempContext.beginPath();

tempContext.arc(...pos, 10, 0, Math.PI * 2);

tempContext.fill();

}

context.clearRect(0, 0, 512, 512);

context.filter = 'blur(5px)';

context.drawImage(tempContext.canvas, 0, 0);

context.filter = 'none';

}

 

render(context, tempContext);

 

requestAnimationFrame(function update() {

render(context, tempContext);

requestAnimationFrame(update);

});


在上面的代码里,我们创建了一个用来绘制不带滤镜的图片的OffscreenCanvas,然后先将图形绘制到这个tempCanvas上,绘制完成后,再将tempCanvas整个图像以带滤镜的方式绘制到原canvas上,这样,我们把多次计算滤镜的操作合并到了1次,从而大大提升了性能。

我们看到,这么做之后,帧率回到了60fps。

【前端冷知识】Canvas 滤镜的性能优化

当然,这种做法实际上只是一种近似绘制,严格上来说,两种绘制是有区别的,但是在blur滤镜上基本是没有问题的,而在其他个别滤镜,例如drop-shadow滤镜,实际上是有问题的,我们用两种写法看一下:

const canvas = document.querySelector('canvas');

const context = canvas.getContext('2d');

 

 

context.filter = 'drop-shadow(5px 5px)';

 

 

function render(context) {

context.clearRect(0, 0, 512, 512);

context.fillStyle = 'blue';

context.beginPath();

context.rect(100, 100, 100, 100);

context.fill();

context.fillStyle = 'red';

context.beginPath();

context.rect(50, 50, 100, 100);

context.fill();

}

 

render(context);


【前端冷知识】Canvas 滤镜的性能优化

const canvas = document.querySelector('canvas');

const context = canvas.getContext('2d');

 

 

const tempCanvas = new OffscreenCanvas(canvas.width, canvas.height);

const tempContext = tempCanvas.getContext('2d');

 

function render(context, tempContext) {

tempContext.clearRect(0, 0, 512, 512);

tempContext.fillStyle = 'blue';

tempContext.beginPath();

tempContext.rect(100, 100, 100, 100);

tempContext.fill();

tempContext.fillStyle = 'red';

tempContext.beginPath();

tempContext.rect(50, 50, 100, 100);

tempContext.fill();

context.clearRect(0, 0, 512, 512);

context.filter = 'drop-shadow(5px 5px)';

context.drawImage(tempContext.canvas, 0, 0);

context.filter = 'none';

}

 

render(context, tempContext);


【前端冷知识】Canvas 滤镜的性能优化

可以看到两个渲染结果并不一样,这是显然的,drop-shadow这样的滤镜,合并渲染会使得两个图形重叠的交界不会形成阴影,所以这种情况,如果要获得正确的效果,那就只能牺牲性能,不能合并滤镜了。

 


 

在寻找靠谱的建站服务?

提供建站全方位托管服务,告诉我你的需求,剩下的交给我帮你处理,建站过程中碰到的任何小问题免费处理,网站所有数据由你自己掌握,还提供网站SEO指导,犹豫什么呢?联系我吧。

 

本文来源于:【前端冷知识】Canvas 滤镜的性能优化-变化吧门户
特别声明:以上文章内容仅代表作者本人观点,不代表变化吧门户观点或立场。如有关于作品内容、版权或其它问题请于作品发表后的30日内与变化吧联系。

  • 赞助本站
  • 微信扫一扫
  • weinxin
  • 加入Q群
  • QQ扫一扫
  • weinxin
二叶草
nginx解析漏洞 优化

nginx解析漏洞

phpstudy(小皮模板存在nginx解析漏洞) 影响版本 phptsuy8.1.07的Nginx1.5.11版本影响版本 phptsuy8.1.07的Nginx1.5.11版本 phpstudy介...
网站SEO优化基础流程(新手必看) 优化

网站SEO优化基础流程(新手必看)

宝塔面板搭建一个获取网站的Favicon图标的APIgetFavicon是一个可以获取网站的Favicon图标并显示在你的网页上的项目。安装方法很简单,属于开箱即食。这篇文章还是基于宝塔面板来搭建。 ...

发表评论