canvas性能优化总结
canvas的主要功能就是用来绘制内容,有时候为了给用户流畅的视觉感受,需要绘制的频率要求很高,这样对绘制的性能就有要求,那么怎么才能写出高性能的绘制代码呢。
尽可能少调用api
例如我们绘制一段线条,如果用如下代码的话,每移动一次就stroke一次:
1 for (var i = 0; i < points.length - 1; i++) {
2 var p1 = points[i];
3 var p2 = points[i + 1];
4 context.beginPath();
5 context.moveTo(p1.x, p1.y);
6 context.lineTo(p2.x, p2.y);
7 context.stroke();
8 }
优化后代码如下,这样beginPah和stroke就少调用了n次。
1 context.beginPath();
2 for (var i = 0; i < points.length - 1; i++) {
3 var p1 = points[i];
4 var p2 = points[i + 1];
5 context.moveTo(p1.x, p1.y);
6 context.lineTo(p2.x, p2.y);
7 }
8 context.stroke();
尽量少改变CANVAS状态机
我们可以改变 context 的若干状态,而几乎所有的渲染操作,最终的效果与 context 本身的状态有关系。例如当对context.lineWidth赋值的话,开销远远大于对一个普通对象赋值的开销。
Canvas 上下文不是一个普通的对象,当调用了 context.lineWidth = 5 时,浏览器会需要立刻地做渲染上下文环境的工作,这样你下次调用诸如 stroke 或 strokeRect 等 API 时,画出来的线就正好是 5 个像素宽了。其实这也是浏览器自身的一种优化,否则如果等到stroke调用时再临时准备渲染环境,会更加影响正常绘制情况下的性能。
下面对比优化前后的代码:
for (var i = 0; i < STRIPES; i++) {
context.fillStyle = (i % 2 ? COLOR1 : COLOR2);
context.fillRect(i * GAP, 0, GAP, 480);
}
context.fillStyle = COLOR1;
for (var i = 0; i < STRIPES / 2; i++) {
context.fillRect((i * 2) * GAP, 0, GAP, 480);
}
context.fillStyle = COLOR2;
for (var i = 0; i < STRIPES / 2; i++) {
context.fillRect((i * 2 + 1) * GAP, 0, GAP, 480);
}
上面两段代码,对fillStyle的调用时机做了改变,提高了性能。
分层canvas
绘制场景复杂的情况下,一般采用多个canvas,可依据绘制内容的频率高低来划分。
如游戏中的背景绘制频率低可以放在一层canvas上,上面的小人等绘制频率高放在一层canvas上,两层canvas的叠加效果达到完整效果。
如下图中绘制过程中的圆形在一层canvas上,不断清除不断绘制,而下面的已经绘制出来的笔迹内容放在另外一层canvas上,不需要清除重绘。
离屏canvas
也叫作预渲染,在离屏canvas上绘制好一整块图形,绘制好后在放到视图canvas中,适合每一帧画图运算复杂的图形。
比如我们有时候为了尽可能少的请求网络资源,会用到精灵图,这样在绘制精灵图某一块内容时,需要利用绘图api的裁剪。
实际发现,使用 drawImage 绘制一张大尺寸图片到较小画布区域上,比起绘制一张和绘制区域尺寸一样大的图片的情形,开销要大一些。可以认为,两者相差的开销正是「裁剪」这一个操作的开销。下面三种绘制方式,性能开销依次增加。
// 将image放到目标canvas指定位置,大小按照原图大小渲染
void ctx.drawImage(image, dx, dy);
// 将image放到目标canvas指定位置,指定宽高渲染
void ctx.drawImage(image, dx, dy, dWidth, dHeight);
// 将image裁剪之后放到目标canvas指定位置,指定宽高渲染
void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
而离屏渲染就可以让我们先把图片裁剪成想要的尺寸内容保存起来,等到真正绘制的时候就可以使用第一种写法简单的把图片绘制出来。
// 在离屏 canvas 上绘制
var offscreencanvas = document.createElement('canvas');
// 宽高赋值为想要的图片尺寸
offscreencanvas.width = dWidth;
offscreencanvas.height = dHeight;
// 裁剪
offscreencanvas.getContext('2d').drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
// 在视图canvas中绘制
viewcontext.drawImage(canvas, x, y);
有时候,游戏对象是多次调用 drawImage 绘制而成,或者根本不是图片,而是使用路径绘制出的矢量形状,那么离屏绘制还能帮你把这些操作简化为一次 drawImage 调用。
组合图形组合了多个图形将它们绘制存放到离屏canvas中,下次未变化的时候直接绘制一次离屏canvas。
裁剪
Canvas (大小一般小于等于屏幕宽高)只是整个大场景下的一个「可视窗口」,如果我们在每一帧中,都把全部内容画出来,势必就会有很多东西画到 Canvas 外面去了,同样调用了绘制 API,但是并没有任何效果。
那么视口外的内容是不需要绘制的,但如果绘制对性能影响有多少呢?进行这样一个实验,绘制一张 320x180 的图片 104 次,当每次都绘制在 Canvas 内部时,消耗了 40ms,而每次都绘制在 Canvas 外时,仅消耗了 8ms。虽然绘制在canvas外时,消耗的时间较短。
但考虑到计算的开销与绘制的开销相差 2~3 个数量级,所以一般情况下通过计算来过滤掉哪些画布外的对象,仍然是很有必要的。
局部重绘
由于 Canvas 的绘制方式是画笔式的,在 Canvas 上绘图时每调用一次 API 就会在画布上进行绘制,一旦绘制就成为画布的一部分。绘制图形时并没有对象保存下来,一旦图形需要更新,需要清除整个画布重新绘制。
如下图仅对红边框的平行四边形做改变,如果每次重绘整个画布内容就不太合适
Canvas 局部刷新的方案:
- 清除指定区域的颜色,并设置 clip
- 所有同这个区域相交的图形重新绘制
要实现局部渲染时,需要考虑的两个因素是:
- 单次刷新时影响的范围最小
- 刷新的图形不会影响其他图形的正确绘制
清除画布内容
有地方说这三种方法性能依次提高,我目前只是使用了clearRect(),没有做个实验对照。
context.fillRect()//颜色填充
context.clearRect(0, 0, w, h)
canvas.width = canvas.width; // 一种画布专用的技巧
坐标值尽量使用整数
避免使用浮点数坐标,使用非整数的坐标绘制内容,系统会自动使用抗锯齿功能,尝试对线条进行平滑处理,这又是一种性能消耗。
可以调用 Math.round 四舍五入取整。
避免大量计算造成阻塞
所谓「阻塞」,可以理解为不间断运行时间超过 16ms 的 JavaScript 代码,导致页面卡顿,丢帧,或者失去响应,这种问题能很快被用户察觉到,造成很差的交互体验。
所以我们要把与渲染无关的大量计算交给worker。大量计算可能造成渲染不流畅,但绝对不能让用户操作卡顿失去响应。
像下图的效果,需要计算大量函数曲线上的点来绘制成曲线,我们移动的时候可以看到计算新点坐标值的过程是有延迟的,但是并不会让用户鼠标拖拽卡顿失效,渲染的过程再跟随鼠标移动。
总结
以上便是总结到的提升绘制效率的几点建议!具体采用哪种需要在实际项目里面根据情况来定,如果你知道这几种方式至少不会大脑空白了!
还有几点开发过程需要注意的:
- 尽可能使用计算代替canvas渲染
- 减少改变 context 的状态,如果要改变请赋值正确的类型,减少浏览器的尝试
- 减少使用 shadowBlur 效果,阴影渲染的性能开销通常比较高
canvas性能优化总结的更多相关文章
- 小程序Canvas性能优化实战
以下内容转载自totoro的文章<小程序Canvas性能优化实战!> 作者:totoro 链接:https://blog.totoroxiao.com/canvas-perf-mini/ ...
- canvas的性能优化
canvas玩多了后,就会自动的要开始考虑性能问题了.怎么优化canvas的动画呢? [使用缓存] 使用缓存也就是用离屏canvas进行预渲染了,原理很简单,就是先绘制到一个离屏canvas中,然后再 ...
- JavaScript性能优化
如今主流浏览器都在比拼JavaScript引擎的执行速度,但最终都会达到一个理论极限,即无限接近编译后程序执行速度. 这种情况下决定程序速度的另一个重要因素就是代码本身. 在这里我们会分门别类的介绍J ...
- (转) Android开发性能优化简介
作者:贺小令 随着技术的发展,智能手机硬件配置越来越高,可是它和现在的PC相比,其运算能力,续航能力,存储空间等都还是受到很大的限制,同时用户对手机的体验要求远远高于PC的桌面应用程序.以上理由,足以 ...
- Android应用性能优化(转)
人类大脑与眼睛对一个画面的连贯性感知其实是有一个界限的,譬如我们看电影会觉得画面很自然连贯(帧率为24fps),用手机当然也需要感知屏幕操作的连贯性(尤其是动画过度),所以Android索性就把达到这 ...
- 移动H5前端性能优化指南
移动H5前端性能优化指南 概述 1. PC优化手段在Mobile侧同样适用2. 在Mobile侧我们提出三秒种渲染完成首屏指标3. 基于第二点,首屏加载3秒完成或使用Loading4. 基于联通3G网 ...
- android 性能优化
本章介绍android高级开发中,对于性能方面的处理.主要包括电量,视图,内存三个性能方面的知识点. 1.视图性能 (1)Overdraw简介 Overdraw就是过度绘制,是指在一帧的时间内(16. ...
- web前端性能优化指南(转)
web前端性能优化指南 概述 1. PC优化手段在Mobile侧同样适用2. 在Mobile侧我们提出三秒种渲染完成首屏指标3. 基于第二点,首屏加载3秒完成或使用Loading4. 基于联通3G网络 ...
- Android性能优化典范第一季
2015年伊始,Google发布了关于Android性能优化典范的专题,一共16个短视频,每个3-5分钟,帮助开发者创建更快更优秀的Android App.课程专题不仅仅介绍了Android系统中有关 ...
随机推荐
- IO、NIO、BIO的区别
我们首先得明白什么是同步,异步,阻塞,非阻塞,只有这几个单个概念理解清楚了,然后在组合理解起来,就相对比较容易了. IO模型主要分类: 同步(synchronous) IO和异步(asynchrono ...
- RabbitMQ(三) SpringBoot2.x 集成 RabbitMQ
3-1 RabbitMQ 整合 SpringBoot2.x 生产者发送消息 创建 SpringBoot 项目application.properties 配置 spring.rabbitmq.host ...
- win10使用cmd命令关闭防火墙
在搜索框内输入cmd,右键选择管理员运行 然后输入: NetSh Advfirewall set allprofiles state off #关闭防火墙 Netsh Advfirewall show ...
- Elasticsearch精进之路:elasticsearch-head插件使用教程
一.elasticsearch-head插件的作用 ealsticsearch是一个分布式.RESTful 风格的搜索和数据分析引擎,所有的数据都是后台服务存储着,类似于Mysql服务器,因此如果我们 ...
- 基于Hi3559AV100 RFCN实现细节解析-(3)系统输入VI分析一 :
下面随笔系列将对Hi3559AV100 RFCN实现细节进行解析,整个过程涉及到VI.VDEC.VPSS.VGS.VO.NNIE,其中涉及的内容,大家可以参考之前我写的博客: Hi3559AV100的 ...
- ACM STU week3
STU ACM训练week3(2.5-2.15) By@Xiezeju 训练计划的CP4配套资源库 训练时间安排 定期任务 任务 每日 进行1小时的盲打训练锻练手速 打字网站,最好注册账号以保存进度 ...
- 基于dlib+django+python 实现web端人脸打卡
face_recognition 基于python+django+dlib实现的人脸打卡系统 开始之前 windows用户需要安装 VS2017 其他VS版本也行 linux用户需要安装c++编译器( ...
- Hi3559AV100 NNIE开发(3)RuyiStudio软件 .wk文件生成过程-mobilefacenet.cfg的参数配置
之后随笔将更多笔墨着重于NNIE开发系列,下文是关于Hi3559AV100 NNIE开发(3)RuyiStudio软件 .wk文件生成过程-mobilefacenet.cfg的参数配置,目前项目需要对 ...
- Why系列:谨慎使用delete
题外话 这里大家可能要笑了,这不就一个操作符吗,还用单独来讲. 有这时间,还不如去看看react源码,vue源码. 我说:react源码会去看的,但是这个也很重要. delete你了解多少 这里提几个 ...
- C# 通过ServiceStack 操作Redis——Set类型的使用及示例
ServiceStack 程序集里面没有方法注解,我在这里将注解添加上去,有不当之处,欢迎指正 Console.WriteLine("---Set类型---"); //添加 set ...