名站网址导航名站网址导航小编为爱好网页hTML5的朋友提供关于网页hTML5的相关知识。
- 本篇文章的具体介绍如下
canvas玩多了后,就会自动的要开始考虑性能问题了。怎么优化canvas的动画呢?
【使用缓存】
使用缓存也就是用离屏canvas进行预渲染了,原理很简单,就是先绘制到一个离屏canvas中,然后再通过drawImage把离屏canvas画到主canvas中。可能看到这很多人就会误解,这不是写游戏里面用的很多的双缓冲机制么?
其实不然,双缓冲机制是游戏编程中为了防止画面闪烁,因此会有一个显示在用户面前的画布以及一个后台画布,进行绘制时会先将画面内容绘制到后台画布中,再将后台画布里的数据绘制到前台画布中。这就是双缓冲,但是canvas中是没有双缓冲的,因为现代浏览器基本上都是内置了双缓冲机制。所依,使用离屏canvas并不是双缓冲,而是把离屏canvas当成一个缓存区。把需要重复绘制的画面数据进行缓存起来,减少相关调用canvas的API的消耗。
众所周知,相关调用canvas的API很消耗性能,所依,当咱们要绘制一些重复的画面数据时,妥善利用离屏canvas对性能方面有很大的提升,可以看下下面的DEMO
1 、 没使用缓存
2、 使用了缓存但是没有设置离屏canvas的宽高
3 、 使用了缓存但是没有设置离屏canvas的宽高
4 、 使用了缓存且设置了离屏canvas的宽高
可以看到上面的DEMO的性能不一样,下面分析一下原因:为了实现每个圈的样式,所依绘制圈圈时我用了循环绘制,如果没用启用缓存,当页面的圈圈数量达到一定时,动画每一帧就要大量相关调用canvas的API,要进行大量的计算,这样再好的浏览器也会被拖垮啦。
XML/网页hTML Code复制内容到剪贴板
- 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();
所依,我的实用具体的方法很简单,每个圈圈对象里面给他一个离屏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");
- thisthis.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();
- }
- }
当我具体相关化圈圈对象时,直接相关调用缓存实用具体的方法,把复杂的圈圈直接画到圈圈对象的离屏canvas中保存起来。
- cache:function(){
- this.cacheCtx.save();
- var j=0;
- this.cacheCtx.lineWidth = borderWidth;
- for(var i=1;i<this.r;i =borderWidth){
- this.cacheCtx.beginPath();
- thisthis.cacheCtx.strokeStyle = this.color[j];
- this.cacheCtx.arc(this.r , this.r , i , 0 , 2*Math.PI);
- this.cacheCtx.stroke();
- j ;
- }
- this.cacheCtx.restore();
- }
然后在接下来的动画中,我只需要把圈圈对象的离屏canvas画到主canvas中,这样,每一帧相关调用的canvasAPI就只有这么一句话:
XML/网页hTML Code 复制内容到剪贴板- 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");
- thisthis.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();
- thisthis.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){
- thisthis.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){
- thisthis.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。直接贴出兼容性写法:
XML/网页hTML Code 复制内容到剪贴板- 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的话相对来说比较消耗性能,那怎样取整呢,可以直接用老外写的很巧妙的实用具体的方法了:
-
JavaScript Code
复制内容到剪贴板
1.rounded = (0.5 somenum) | 0;
2.rounded = ~~ (0.5 somenum); 3.rounded = (0.5 somenum) << 0;
运算符不懂的可以直接戳:http://www.w3school.com.cn/js/pro_js_operators_bitwise.asp 里面有详细解释
【尽量减少canvasAPI的相关调用】
作粒子具体效果时,尽量少使用圆,最好使用方形,因为粒子太小,所依方形看上去也跟圆差不多。至于原因,很容易理解,咱们画一个圆需要三个步骤:先beginPath,然后用arc画弧,再用fill进行填充才能产生一个圆。但是画方形,只需要一个fillRect就可以了。虽然只是差了两个相关调用,当粒子对象数量达到一定时,这性能差距就会显示出来了。
还有一些其他注意事项,我就不一一列举了,因为谷歌上一搜也挺多的。我这也算是一个给自己做下记录,主要是记录缓存的用法。想要提升canvas的性能最主要的还是得注意具体代码的结构,减少不必要的API相关调用,在每一帧中减少复杂的运算或者把复杂运算由每一帧算一次改成数帧算一次。同时,上面所述的缓存用法,我因为贪图方便,所依是每个对象一个离屏canvas,其实离屏canvas也不能用的太泛滥,如果用太多离屏canvas也会有性能问题,请尽量合理利用离屏canvas。
源码地址:https://github.com/whxaxes/canvas-test/tree/gh-pages/src/Other-demo/cache
关于网页hTML5的相关知识就说到这里希望可以帮助朋友们。,网页hTML5,Canvas利用网页hTML5教程 Canvas制作一个简单的打飞机游戏
闲话不多说,先上DEMO撒:飞机游戏 楼主写这个人纯碎娱乐,没想着写成多正式的游戏哈。