最近几天没有及时更新,是因为这几天在忙一个项目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();

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

?? 但是,滤镜是一种比较消耗性能的操作,尤其是类似于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。

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

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

看到帧率在我的电脑上已经下降到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。

当然,这种做法实际上只是一种近似绘制,严格上来说,两种绘制是有区别的,但是在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);

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);

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

以上是今天讨论的内容,关于Canvas的滤镜优化,还有什么问题,欢迎在issue中讨论。

在后续文章中,我们有机会进一步讨论在WebGL上基于Shader实现的滤镜和性能的优化。


最后插播一条广告:

我将于9月21日去成都FEDay分享主题《你不知道的GPU——前端、图形系统与数据可视化》

有兴趣的同学可以去参加~ 报名地址:https://fequan.com/2019

关于奇舞周刊

《奇舞周刊》是360公司专业前端团队「奇舞团」运营的前端技术社区。关注公众号后,直接发送链接到后台即可给我们投稿。

canvas刷新_【前端冷知识】Canvas 滤镜的性能优化相关推荐

  1. 前端里的button怎么去除点击自带边框_前端不为人知的一面--前端冷知识集锦

    前端已经被玩儿坏了!像console.log()可以向控制台输出图片等炫酷的玩意已经不是什么新闻了,像用||操作符给变量赋默认值也是人尽皆知的旧闻了,今天看到Quora上一个帖子,瞬间又GET了好多前 ...

  2. 这些鲜为人知的前端冷知识,你都GET了吗?

    来源:猴哥说前端‍‍‍‍‍‍ 背景 最近公司项目不多,比较清闲,划水摸鱼混迹于各大技术博客平台,瞬间又GET了好多前端技能,一些属于技巧,一些则是闻所未闻的冷知识,一时间还消化不过来,不由的发出一声感 ...

  3. web前端开发,如何提高页面性能优化?

    web前端开发,如何提高页面性能优化? 内容方面 减少HTTP请求次数 减少DOM操作 减少DNS查询 使用Ajax 可缓存 css方面 把css样式HTML代码页的上端 从页面中分离css代码,从外 ...

  4. 前端wxml取后台js变量值_这些鲜为人知的前端冷知识,你都GET了吗?

    背景 最近公司项目不多,比较清闲,划水摸鱼混迹于各大技术博客平台,瞬间又GET了好多前端技能,一些属于技巧,一些则是闻所未闻的冷知识,一时间还消化不过来,不由的发出一声感叹! 前端可真是博大精深 于是 ...

  5. 前端实现可绘制的canvas画布_前端图形学基础(五)——Canvas状态管理

    点击右上角的关注,不定期前端干货分享!! 欢迎来到我的前端图形学系列文章: 前端图形学基础(一)--Canvas基础入门 前端图形学基础(二)--Canvas基础 前端图形学基础(三)--Canvas ...

  6. excel判断字符串包含另一个字符串_【前端冷知识】如何正确判断一个字符串是数值?...

    在网页中,我们从用户输入的内容中获取的值通常是字符串,但是有时候我们希望用户输入的内容一定要能转成数值: <input id="userInput"> userInpu ...

  7. 前端不为人知的一面--前端冷知识集锦

    转自:http://www.cnblogs.com/Wayou/p/things_you_dont_know_about_frontend.html 前端已经被玩儿坏了!像console.log()可 ...

  8. java canvas 画图片_[Java教程][HTML5] Canvas绘制简单图片

    [Java教程][HTML5] Canvas绘制简单图片 0 2016-05-13 13:00:04 获取Image对象,new出来 定义Image对象的src属性,参数:图片路径 定义Image对象 ...

  9. canvas 插件_基于Angular的Canvas手写签名插件

    灵感来源 之前, 在轻流的业务中遇到了一个需求, 是能够让客户使用手写签名的功能. 签名演示 问题来了, 这...我不会啊! 这得是Canvas了吧. 正所谓, 插件用的好, 下班走的早. 于是我就开 ...

最新文章

  1. vmstat 命令的使用
  2. 触摸传感器的电路图符号_光电传感器电路图以及应用和优点
  3. LeetCode练习及自己理解记录(1)
  4. 分享博文摘要图标【11/16更新】
  5. LeetCode 968. 监控二叉树(DFS)
  6. 你可能也会掉进这个简单的 String 的坑
  7. JDBC_ResultSet类_结果集对象
  8. HTML5新增的表单元素有哪些?
  9. java复习即基础知识点 思维导图
  10. LINQ查询表达式详解(2)——查询表达式的转换
  11. vue router hash和history的区别_react-router-v4
  12. JSP九大内置对象总结
  13. Unity VSCode + Emmy Lua 插件断点调试Lua脚本
  14. 大一 C语言 实验1
  15. 深透研究病毒3—威金病毒
  16. 线程生命周期与创建线程的多种方式
  17. HackTheBox-Spider WP
  18. Pycharm typo PEP 8
  19. django email邮箱
  20. gmsv源代码c语言,石器时代ABLua的原理、简介、过程、运用、收发

热门文章

  1. 腾讯广告算法大赛官方“开挂”,为你直播赛题解析!
  2. 腾讯广告算法大赛 | 第一周周冠军心得分享
  3. 多类目MoE模型在京东电商搜索中的应用
  4. elipse调试linux内核,debug eclipse cdt + qemu虚拟机调试linux内核
  5. Windows RDP协议重大漏洞后发现黑客开始大规模扫瞄
  6. @Autowired的作用
  7. Confluence 6 匿名访问远程 API
  8. 写程序没思路怎么办?
  9. c语言 单词变复数_关于C语言中的Complex(复数类型)和imaginary(虚数类型)
  10. db2数据库日期减一天_DB2 日期时间函数