canvas玩多了后,就会自动的要开始考虑性能问题了。怎么优化canvas的动画呢?

【使用缓存】

使用缓存也就是用离屏canvas进行预渲染了,原理很简单,就是先绘制到一个离屏canvas中,然后再通过drawImage把离屏canvas画到主canvas中。可能看到这很多人就会误解,这不是写游戏里面用的很多的双缓冲机制么?

其实不然,双缓冲机制是游戏编程中为了防止画面闪烁,因此会有一个显示在用户面前的画布以及一个后台画布,进行绘制时会先将画面内容绘制到后台画布中,再将后台画布里的数据绘制到前台画布中。这就是双缓冲,但是canvas中是没有双缓冲的,因为现代浏览器基本上都是内置了双缓冲机制。所以,使用离屏canvas并不是双缓冲,而是把离屏canvas当成一个缓存区。把需要重复绘制的画面数据进行缓存起来,减少调用canvas的API的消耗。

众所周知,调用canvas的API很消耗性能,所以,当我们要绘制一些重复的画面数据时,妥善利用离屏canvas对性能方面有很大的提升,可以看下下面的DEMO

1、没使用缓存(100个圈圈对象)

2、使用了缓存但是没有设置离屏canvas的宽高(200个圈圈对象)

3、使用了缓存但是没有设置离屏canvas的宽高(500个圈圈对象)

4、使用了缓存且设置了离屏canvas的宽高(1000个圈圈对象)

可以看到上面的DEMO的性能不一样,下面分析一下原因:为了实现每个圈的样式,所以绘制圈圈时我用了循环绘制,如果没用启用缓存,当页面的圈圈数量达到一定时,动画每一帧就要大量调用canvas的API,要进行大量的计算,这样再好的浏览器也会被拖垮啦。

                        ctx.save();
var j=0;
ctx.lineWidth = borderWidth;
for(var i=1;i 所以,我的方法很简单,每个圈圈对象里面给他一个离屏canvas作缓存区。 除了创建离屏canvas作为缓存之外,下面的代码中有一点很关键,就是要设置离屏canvas的宽度和高度,canvas生成后的默认大小是300X150;对于我的代码中每个缓存起来圈圈对象半径最大也就不超过80,所以300X150的大小明显会造成很多空白区域,会造成资源浪费,所以就要设置一下离屏canvas的宽度和高度,让它跟缓存起来的元素大小一致,这样也有利于提高动画性能。上面的四个demo很明显的显示出了性能差距,如果没有设置宽高,当页面超过400个圈圈对象时就会卡的不行了,而设置了宽高1000个圈圈对象也不觉得卡。
var ball = function(x , y , vx , vy , useCache){
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.r = getZ(getRandom(20,40));
this.color = [];
this.cacheCanvas = document.createElement("canvas");
this.cacheCtx = this.cacheCanvas.getContext("2d");
this.cacheCanvas.width = 2*this.r;
this.cacheCanvas.height = 2*this.r;
var num = getZ(this.r/borderWidth);
for(var j=0;j 当我实例化圈圈对象时,直接调用缓存方法,把复杂的圈圈直接画到圈圈对象的离屏canvas中保存起来。
               cache:function(){
this.cacheCtx.save();
var j=0;
this.cacheCtx.lineWidth = borderWidth;
for(var i=1;i 然后在接下来的动画中,我只需要把圈圈对象的离屏canvas画到主canvas中,这样,每一帧调用的canvasAPI就只有这么一句话:
ctx.drawImage(this.cacheCanvas , this.x-this.r , this.y-this.r);

跟之前的for循环绘制比起来,实在是快太多了。所以当需要重复绘制矢量图的时候或者绘制多个图片的时候,我们都可以合理利用离屏canvas来预先把画面数据缓存起来,在接下来的每一帧中就能减少很多没必要的消耗性能的操作。

下面贴出1000个圈圈对象流畅版代码:

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
body{
padding:0;
margin:0;
overflow: hidden;
}
#cas{
display: block;
background-color:rgba(0,0,0,0);
margin:auto;
border:1px solid;
}
</style>
<title>测试</title>
</head>
<body>
<div >
<canvas id='cas' width="800" height="600">浏览器不支持canvas</canvas>
<div style="text-align:center">1000个圈圈对象也不卡</div>
</div> <script>
var testBox = function(){
var canvas = document.getElementById("cas"),
ctx = canvas.getContext('2d'),
borderWidth = 2,
Balls = [];
var ball = function(x , y , vx , vy , useCache){
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.r = getZ(getRandom(20,40));
this.color = [];
this.cacheCanvas = document.createElement("canvas");
this.cacheCtx = this.cacheCanvas.getContext("2d");
this.cacheCanvas.width = 2*this.r;
this.cacheCanvas.height = 2*this.r;
var num = getZ(this.r/borderWidth);
for(var j=0;j<num;j++){
this.color.push("rgba("+getZ(getRandom(0,255))+","+getZ(getRandom(0,255))+","+getZ(getRandom(0,255))+",1)");
}
this.useCache = useCache;
if(useCache){
this.cache();
}
} function getZ(num){
var rounded;
rounded = (0.5 + num) | 0;
// A double bitwise not.
rounded = ~~ (0.5 + num);
// Finally, a left bitwise shift.
rounded = (0.5 + num) << 0; return rounded;
} ball.prototype = {
paint:function(ctx){
if(!this.useCache){
ctx.save();
var j=0;
ctx.lineWidth = borderWidth;
for(var i=1;i<this.r;i+=borderWidth){
ctx.beginPath();
ctx.strokeStyle = this.color[j];
ctx.arc(this.x , this.y , i , 0 , 2*Math.PI);
ctx.stroke();
j++;
}
ctx.restore();
} else{
ctx.drawImage(this.cacheCanvas , this.x-this.r , this.y-this.r);
}
}, cache:function(){
this.cacheCtx.save();
var j=0;
this.cacheCtx.lineWidth = borderWidth;
for(var i=1;i<this.r;i+=borderWidth){
this.cacheCtx.beginPath();
this.cacheCtx.strokeStyle = this.color[j];
this.cacheCtx.arc(this.r , this.r , i , 0 , 2*Math.PI);
this.cacheCtx.stroke();
j++;
}
this.cacheCtx.restore();
}, move:function(){
this.x += this.vx;
this.y += this.vy;
if(this.x>(canvas.width-this.r)||this.x<this.r){
this.x=this.x<this.r?this.r:(canvas.width-this.r);
this.vx = -this.vx;
}
if(this.y>(canvas.height-this.r)||this.y<this.r){
this.y=this.y<this.r?this.r:(canvas.height-this.r);
this.vy = -this.vy;
} this.paint(ctx);
}
} var Game = {
init:function(){
for(var i=0;i<1000;i++){
var b = new ball(getRandom(0,canvas.width) , getRandom(0,canvas.height) , getRandom(-10 , 10) , getRandom(-10 , 10) , true)
Balls.push(b);
}
}, update:function(){
ctx.clearRect(0,0,canvas.width,canvas.height);
for(var i=0;i<Balls.length;i++){
Balls[i].move();
}
}, loop:function(){
var _this = this;
this.update();
RAF(function(){
_this.loop();
})
}, start:function(){
this.init();
this.loop();
}
} window.RAF = (function(){
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {window.setTimeout(callback, 1000 / 60); };
})(); return Game;
}(); function getRandom(a , b){
return Math.random()*(b-a)+a;
} window.onload = function(){
testBox.start();
}
</script>
</body>
</html>

离屏canvas还有一个注意事项,如果你做的效果是会将对象不停地创建和销毁,请慎重使用离屏canvas,至少不要像我上面写的那样给每个对象的属性绑定离屏canvas。

因为如果这样绑定,当对象被销毁时,离屏canvas也会被销毁,而大量的离屏canvas不停地被创建和销毁,会导致canvas buffer耗费大量GPU资源,容易造成浏览器崩溃或者严重卡帧现象。解决办法就是弄一个离屏canvas数组,预先装进足够数量的离屏canvas,仅将仍然存活的对象缓存起来,当对象被销毁时,再解除缓存。这样就不会导致离屏canvas被销毁了。

【使用requestAnimationFrame】

这个就不具体解释了,估计很多人都知道,这个才是做动画的最佳循环,而不是setTimeout或者setInterval。直接贴出兼容性写法:

window.RAF = (function(){
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {window.setTimeout(callback, 1000 / 60); };
})();

【避免浮点运算】

虽然javascript提供了很方便的一些取整方法,像Math.floor,Math.ceil,parseInt,但是,国外友人做过测试,parseInt这个方法做了一些额外的工作(比如检测数据是不是有效的数值,parseInt 甚至先将参数转换成了字符串!),所以,直接用parseInt的话相对来说比较消耗性能,那怎样取整呢,可以直接用老外写的很巧妙的方法了:

rounded = (0.5 + somenum) | 0;
rounded = ~~ (0.5 + somenum);
rounded = (0.5 + somenum)

运算符不懂的可以直接戳:http://www.w3school.com.cn/js/pro_js_operators_bitwise.asp  里面有详细解释

【尽量减少canvasAPI的调用】

作粒子效果时,尽量少使用圆,最好使用方形,因为粒子太小,所以方形看上去也跟圆差不多。至于原因,很容易理解,我们画一个圆需要三个步骤:先beginPath,然后用arc画弧,再用fill进行填充才能产生一个圆。但是画方形,只需要一个fillRect就可以了。虽然只是差了两个调用,当粒子对象数量达到一定时,这性能差距就会显示出来了。

还有一些其他注意事项,我就不一一列举了,因为谷歌上一搜也挺多的。我这也算是一个给自己做下记录,主要是记录缓存的用法。想要提升canvas的性能最主要的还是得注意代码的结构,减少不必要的API调用,在每一帧中减少复杂的运算或者把复杂运算由每一帧算一次改成数帧算一次。同时,上面所述的缓存用法,我因为贪图方便,所以是每个对象一个离屏canvas,其实离屏canvas也不能用的太泛滥,如果用太多离屏canvas也会有性能问题,请尽量合理利用离屏canvas。

相关链接

canvas的性能优化的更多相关文章

  1. 谈谈canvas的性能优化(主要讲缓存问题)

    声明:本文为原创文章,如需转载,请注明来源WAxes,谢谢! canvas玩多了后,就会自动的要开始考虑性能问题了.怎么优化canvas的动画呢? [使用缓存] 使用缓存也就是用离屏canvas进行预 ...

  2. 移动H5前端性能优化指南

    移动H5前端性能优化指南 概述 1. PC优化手段在Mobile侧同样适用2. 在Mobile侧我们提出三秒种渲染完成首屏指标3. 基于第二点,首屏加载3秒完成或使用Loading4. 基于联通3G网 ...

  3. 移动H5前端性能优化指南[转]

    移动H5前端性能优化指南 米随随2015.01.23 移动H5前端性能优化指南 概述 1. PC优化手段在Mobile侧同样适用2. 在Mobile侧我们提出三秒种渲染完成首屏指标3. 基于第二点,首 ...

  4. 移动 H5(PC Web)前端性能优化指南

    原文地址https://zhuanlan.zhihu.com/p/25176904?utm_source=wechat_session&utm_medium=social&utm_me ...

  5. [转]移动H5前端性能优化指南

    移动H5前端性能优化指南 概述 1. PC优化手段在Mobile侧同样适用2. 在Mobile侧我们提出三秒种渲染完成首屏指标3. 基于第二点,首屏加载3秒完成或使用Loading4. 基于联通3G网 ...

  6. 小程序Canvas性能优化实战

    以下内容转载自totoro的文章<小程序Canvas性能优化实战!> 作者:totoro 链接:https://blog.totoroxiao.com/canvas-perf-mini/ ...

  7. JavaScript性能优化

    如今主流浏览器都在比拼JavaScript引擎的执行速度,但最终都会达到一个理论极限,即无限接近编译后程序执行速度. 这种情况下决定程序速度的另一个重要因素就是代码本身. 在这里我们会分门别类的介绍J ...

  8. (转) Android开发性能优化简介

    作者:贺小令 随着技术的发展,智能手机硬件配置越来越高,可是它和现在的PC相比,其运算能力,续航能力,存储空间等都还是受到很大的限制,同时用户对手机的体验要求远远高于PC的桌面应用程序.以上理由,足以 ...

  9. Android应用性能优化(转)

    人类大脑与眼睛对一个画面的连贯性感知其实是有一个界限的,譬如我们看电影会觉得画面很自然连贯(帧率为24fps),用手机当然也需要感知屏幕操作的连贯性(尤其是动画过度),所以Android索性就把达到这 ...

随机推荐

  1. 获取web.config 内的值

    获取 System.Configuration.ConfigurationManager.AppSettings[DrugPackageRegistrationName]//获取web.config ...

  2. (笔记)JQuery扩展方法实现Form表单与Json互相转换

    JQuery笔记 记两段代码,使用JQuery实现从表单获取json与后端交互,以及把后端返回的json映射到表单相应的字段上. 把表单转换出json对象 //把表单转换出json对象 $.fn.to ...

  3. ASP.NET中几种加密方法

    下面就是ASP.NET中几种加密方法.加密算法有两种,也就是上面提到的MD5和SHA1,这里我举的例子是以MD5为例,SHA1大致相同,只是使用的类不一样. MD5的全称是Message-Digest ...

  4. You're Given a String...

    You're given a string of lower-case Latin letters. Your task is to find the length of its longest su ...

  5. 重温CLR(十) 字符、字符串和文本处理

    本章将介绍.net中处理字符和字符串的机制 字符 在.NET Framewole中,字符总是表示成16位Unicode代码值,这简化了国际化应用程序的开发. 每个字符都表示成System.Char结构 ...

  6. 微信小程序(1)——小程序的特点以及结构

    简单的,用完即走的应用 低频应用 性能要求不高的应用 应用程序入口(app.js   app.json  app.wxss) 一级页面:wxml,wxss,js,json 二级页面:wxml,wxss ...

  7. 《DSP using MATLAB》示例Example7.9

    代码: wp = 0.2*pi; ws = 0.3*pi; As = 50; tr_width = ws - wp; M = ceil((As-7.95)/(2.285*tr_width) + 1 ) ...

  8. 【DUBBO】zookeeper在dubbo中作为注册中心的原理结构

    [一]原理图 [二]原理图解释 流程:1.服务提供者启动时向/dubbo/com.foo.BarService/providers目录下写入URL2.服务消费者启动时订阅/dubbo/com.foo. ...

  9. mysql 安装(压缩包安装和exe安装)

    1:mysql官网:https://dev.mysql.com/downloads/file/?id=482487 2:压缩包安装:https://www.cnblogs.com/jamespan23 ...

  10. nginx 缓存处理

    核心指令   proxy_cache_path /data/nginx/cache/one levels=1:2 keys_zone=one:10m max_size=10g; proxy_cache ...