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系统中有关 ...
随机推荐
- Ability之间或者进程间数据传递之对象(Sequenceable序列化)
鸿蒙入门指南,小白速来!0基础学习路线分享,高效学习方法,重点答疑解惑--->[课程入口] 这两天51cto上的一个粉丝朋友问了我一个问题,Ability之间使用Sequenceable序列化传 ...
- TextView 的append后面 马上调用fullScroll(),会发现无法滚动到真正的底部
如果在TextView的append后面马上调用fullScroll,会发现无法滚动到真正的底部,这是因为Android下很多(如果不是全部的话)函数都是基于消息的,用消息队列来保证同步,所以函数调用 ...
- nacos服务注册与发现之客户端
服务注册 1.1 NamingService.registerInstance的方法为客户端提供的服务注册接口 1.2 客户端通过调用NamingService.registerService上报到n ...
- Python3+pygame实现的俄罗斯方块 代码完整 有演示效果
一.简单说明 80.90后的小伙伴都玩过"俄罗斯方块",那种"叱咤风云"场景 偶尔闪现在脑海 真的是太爽了:如果没有来得及玩过的同学,这次可以真正的自己做一个了 ...
- 在不使用外延层的同轴半绝缘衬底材料上制作4H-SIC横向双重注入金属氧化物半导体场效应晶体管
在不使用外延层的同轴半绝缘衬底材料上制作4H-SIC横向双重注入金属氧化物半导体场效应晶体管 杂志:日本应用物理杂志 在不使用外延层在同轴的半绝缘SIC衬底上制作4H-SIC横向双重注入金属氧化物 ...
- Nebula Storage 2.0 存储格式
随着 2.0 各版本的陆续发布,Nebula Graph 迎来了一系列的改动,在存储方面,影响最大的改动就是底层编码格式进行了修改.Nebula Graph 的底层存储是基于 KV 保存在 Rocks ...
- [MongoDB知识体系] 一文全面总结MongoDB知识体系
MongoDB教程 - Mongo知识体系详解 本系列将给大家构建MongoDB全局知识体系.@pdai MongoDB教程 - Mongo知识体系详解 知识体系 学习要点 学习资料 官网资料 入门系 ...
- Intellij IDEA设置默认字符编码
file---settings--editor--file encoding里面设置
- flutter资料
Flutter社区和资源传送门 新: 慕课网<Flutter入门与案例实战> | 中文网<Flutter实战>电子书 字体图标生成 http://fluttericon ...
- Python中面向对象的概念
1.语言的分类 1)面向机器 抽象成机器指令,机器容易理解.代表:汇编语言. 2)面向过程 做一件事,排除步骤,第一步做什么,第二步做什么,如果出现A问题,做什么处理,出现b问题,做什么处理.问题规模 ...