HTML5 Canvas性能分析与提升技巧

canvas代码也是有一些优化技巧的,我之前的几篇canvas的文章单纯涉及如何做使用canvas,下面看看别人是怎么说优化canvas性能的。

一:使用缓存技术实现预绘制,减少重复绘制Canvs内容

很多时候我们在Canvas上绘制与更新,总是会保留一些不变的内容,对于这些内容

应该预先绘制缓存,而不是每次刷新。

直接绘制代码如下:

context.font="24px Arial";
context.fillStyle="blue";
context.fillText("Please press <Esc> to exit game",5,50);
requestAnimationFrame(render);
使用缓存预绘制技术:

function render(context) {
 context.drawImage(mText_canvas, 0, 0);
 requestAnimationFrame(render);
}

function drawText(context) {
 mText_canvas = document.createElement("canvas");
 mText_canvas.width = 450;
 mText_canvas.height = 54;
 var m_context = mText_canvas.getContext("2d");
 m_context.font="24px Arial";
 m_context.fillStyle="blue";
 m_context.fillText("Please press <Esc> to exit game",5,50);
}
使用Canvas缓存绘制技术的时候,一定记得缓存Canvas对象大小要小于实际的Canvas

大小。尽量把绘制直线点的操作放在一起,而且尽量一次绘制完成,一个不好的代码如下:

for (var i = 0; i < points.length - 1; i++) {
   var p1 = points[i];
   var p2 = points[i+1];
   context.beginPath();
   context.moveTo(p1.x, p1.y);
   context.lineTo(p2.x, p2.y);
   context.stroke();
}
修改以后性能较高的代码如下:

context.beginPath();
for (var i = 0; i < points.length - 1; i++) {
  var p1 = points[i];
  var p2 = points[i+1];
  context.moveTo(p1.x, p1.y);
  context.lineTo(p2.x, p2.y);
}
context.stroke();
避免不必要的Canvas绘制状态频繁切换,一个频繁切换绘制style的例子如下:

var GAP = 10;
for(var i=0; i<10; i++) {
 context.fillStyle = (i % 2 ? "blue" : "red");
 context.fillRect(0, i * GAP, 400, GAP);
}
避免频繁切换绘制状态,性能更好的绘制代码如下:

// even
context.fillStyle = "red";
for (var i = 0; i < 5; i++) {
context.fillRect(0, (i*2) * GAP, 400, GAP);
}
   
// odd
context.fillStyle = "blue";
for (var i = 0; i < 5; i++) {
context.fillRect(0, (i*2+1) * GAP, 400, GAP);
}

绘制时,只绘制需要更新的区域,任何时候都要避免不必要的重复绘制与额外开销。

对于复杂的场景绘制使用分层绘制技术,分为前景与背景分别绘制。定义Canvas层的

html如下:

<canvas id="bg" width="640" height="480" style="position: absolute; z-index: 0">
</canvas>
<canvas id="fg" width="640" height="480" style="position: absolute; z-index: 1">
</canvas>
如果没有必要,要尽量避免使用绘制特效,如阴影,模糊等。

避免使用浮点数坐标。

绘制图形时,长度与坐标应选取整数而不是浮点数,原因在于Canvas支持半个像素绘制

会根据小数位实现插值算法实现绘制图像的反锯齿效果,如果没有必要请不要选择浮点数值。

清空Canvas上的绘制内容:

context.clearRect(0, 0, canvas.width,canvas.height)

但是其实在Canvas还有一种类似hack的清空方法:

canvas.width = canvas.width;

也可以实现清空canvas上内容的效果,但是在某些浏览器上可能不支持。

性能测试

为了处理飞速变化着的HTML5 canvas,JSPerf(jsperf.com) 测试证明了我们在文中提到的每一个方法目前都还生效。JSPerf是一个十分有用的web应用程序,web开发者们可以利用此程序编写 JavaScript 性能测试用例。每一个测试用例只关注你企图达到的某一方面的结果(比如说清楚画布),每一个这样的测试用例包含有若干个能够达到同一结果的不同方法。 JSPerf在一小段时间内尽可能多的运行每一个方法,并没给出一个统计学上有显著意义的每秒中迭代次数。高分意味着更高的性能。

浏览者可以在自己的浏览器中打开JSPerf 性能测试页面,而且可以让JSPerf把标准化的测试结果存储在Browserscope(broserscope.org)中。因为本文中提到的优化技术已经备份到了JSPerf的结果中,因此你可以重新运行查看最新的信息以确定相应的方法是否还有效。我已经写了一个小的帮助应用程序(helper applicatin)来将测试的结果绘制成图表嵌入到整篇文章中。

本文中的性能测试结果与特定的浏览器版本有很重要的关系。由于我们不知到您用的浏览器运行在什么操作系统上,更重要的是也不知道在您进行这些测试时 HTML5 canvas是否被硬件加速。你可以在Chrome浏览器地址栏中使用 about:gpu 命令来查看Chrome的HTML5 canvas 是否被硬件加速。

1.PRE-RENDER TO AN OFF-SCREEN CANVAS

 我们在写一个游戏的时候常常会遇到在多个连续的帧中重绘相似的物体的情况。在这中情况下,你可以通过预渲染场景中的大部分物体来获取巨大的性能提 升。预渲染即在一个或者多个临时的不会在屏幕上显示的canvas中渲染临时的图像,然后再把这些不可见的canvas作为图像渲染到可见的canvas 中。对于计算机图形学比较熟悉的朋友应该都知道,这个技术也被称做display list。

例如,假定你在重绘以每秒60帧运行的Mario。你既可以在每一帧重绘他的帽子、胡子和“M”也可以在运行动画前预渲染Mario。

没有预渲染的情况:

// canvas, context are defined 
function render() { 
  drawMario(context); 
  requestAnimationFrame(render); 

预渲染的情况:

var m_canvas = document.createElement('canvas'); 
m_canvas.width = 64; 
m_canvas.height = 64; 
var m_context = m_canvas.getContext(‘2d’); 
drawMario(m_context); 
function render() { 
  context.drawImage(m_canvas, 0, 0); 
  requestAnimationFrame(render); 

关于requestAnimationFrame的使用方法将在后续部分做详细的讲述。下面的图标说明了显示了利用预渲染技术所带来的性能改善情况。(来自于jsperf):

当渲染操作(例如上例中的drawmario)开销很大时该方法将非常有效。其中很耗资源的文本渲染操作就是一个很好的例子。从下表你可以看到利用预渲染操作所带来的强烈的性能提升。(来自于jsperf):

然而,观察上边的例子我们可以看出松散的预渲染(pre-renderde loose)性能很差。当使用预渲染的方法时,我们要确保临时的canvas恰好适应你准备渲染的图片的大小,否则过大的canvas会导致我们获取的性 能提升被将一个较大的画布复制到另外一个画布的操作带来的性能损失所抵消掉。

上述的测试用例中紧凑的canvas相当的小:

can2.width = 100; 
can2.height = 40; 
如下宽松的canvas将导致糟糕的性能:

can3.width = 300; 
can3.height = 100; 

2.BATCH CANVAS CALLS TOGETHER

因为绘图是一个代价昂贵的操作,因此,用一个长的指令集载入将绘图状态机载入,然后再一股脑的全部写入到video缓冲区。这样会会更佳有效率。

例如,当需要画对条线条时先创建一条包含所有线条的路经然后用一个draw调用将比分别单独的画每一条线条要高效的多:

or (var i = 0; i < points.length - 1; i++) { 
  var p1 = points[i]; 
  var p2 = points[i+1]; 
  context.beginPath(); 
  context.moveTo(p1.x, p1.y); 
  context.lineTo(p2.x, p2.y); 
  context.stroke(); 

通过绘制一个包含多条线条的路径我们可以获得更好的性能:

ontext.beginPath(); 
for (var i = 0; i < points.length - 1; i++) { 
  var p1 = points[i]; 
  var p2 = points[i+1]; 
  context.moveTo(p1.x, p1.y); 
  context.lineTo(p2.x, p2.y); 

context.stroke(); 
这个方法也适用于HTML5 canvas。比如,当我们画一条复杂的路径时,将所有的点放到路径中会比分别单独的绘制各个部分要高效的多(jsperf):

然而,需要注意的是,对于canvas来说存在一个重要的例外情况:若欲绘制的对象的部件中含有小的边界框(例如,垂直的线条或者水平的线条),那么单独的渲染这些线条或许会更加有效(jsperf):

3.AVOID UNNECESSARY CANVAS STATE CHANGES

HTML5 canvas元素是在一个状态机之上实现的。状态机可以跟踪诸如fill、stroke-style以及组成当前路径的previous points等等。在试图优化绘图性能时,我们往往将注意力只放在图形渲染上。实际上,操纵状态机也会导致性能上的开销。

例如,如果你使用多种填充色来渲染一个场景,按照不同的颜色分别渲染要比通过canvas上的布局来进行渲染要更加节省资源。为了渲染一副条纹的图案,你可以这样渲染:用一种颜色渲染一条线条,然后改变颜色,渲染下一条线条,如此反复:

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

下面的性能测试用例分别用上边两种方法绘制了一副交错的细条纹图案(jsperf):

正如我们预期的,交错改变状态的方法要慢的多,原因是变化状态机是有额外开销的。

4.RENDER SCREEN DIFFERENCES ONLY, NOT THE WHOLE  NEW STATE

这个很容易理解,在屏幕上绘制较少的东西要比绘制大量的东西节省资源。重绘时如果只有少量的差异你可以通过仅仅重绘差异部分来获得显著的性能提升。换句话说,不要在重绘前清除整个画布。:

context.fillRect(0, 0, canvas.width, canvas.height); 
跟踪已绘制部分的边界框,仅仅清理这个边界之内的东西:

context.fillRect(last.x, last.y, last.width, last.height); 
下面的测试用例说明了这一点。该测试用例中绘制了一个穿过屏幕的白点(jsperf):

如果您对计算机图形学比较熟悉,你或许应该知道这项技术也叫做“redraw technique”,这项技术会保存前一个渲染操作的边界框,下一次绘制前仅仅清理这一部分的内容。

这项技术也适用于基于像素的渲染环境。这篇名为JavaScript NIntendo emulator tallk的文章说明了这一点。

5.USE MUTIPLE LAYERED CANVASES FOR COMPLEX SCENES

我们前边提到过,绘制一副较大的图片代价是很高昂的因此我们应尽可能的避免。除了前边讲到的利用另外得不可见的canvas进行预渲染外,我们也可以叠在 一起的多层canvas。图哦你的过利用前景的透明度,我们可以在渲染时依靠GPU整合不同的alpha值。你可以像如下这么设置,两个绝对定位的 canvas一个在另一个的上边:

<canvas id="bg" width="640" height="480" style="position: absolute; z-index: 0"> 
</canvas> 
<canvas id="fg" width="640" height="480" style="position: absolute; z-index: 1"> 
</canvas> 
相对于仅仅有一个canvas的情况来讲,这个方法的优势在于,当我们需要绘制或者清理前景canvas时,我们不需要每次都修改背景 canvas。如果你的游戏或者多媒体应用可以分成前景和背景这样的情况,那么请考虑分贝渲染前景和背景来获取显著的性能提升。下面的图表比较了只有一个 canvas的情况和有前景背景两个canvas而你只需要清理和重绘前景的情况(jsperf):

你可以用相较慢的速度(相对于前景)来渲染背景,这样便可利用人眼的一些视觉特性达到一定程度的立体感,这样会更吸引用户的眼球。比如,你可以在每一帧中渲染前景而仅仅每N帧才渲染背景。

注意,这个方法也可以推广到包含更多canvas曾的复合canvas。如果你的应用利用更多的曾会运行的更好时请利用这种方法。

6.AVOID SHADOWBLUR

跟其他很多绘图环境一样,HTML5 canvas允许开发者对绘图基元使用阴影效果,然而,这项操作是相当耗费资源的。

context.shadowOffsetX = 5; 
context.shadowOffsetY = 5; 
context.shadowBlur = 4; 
context.shadowColor = 'rgba(255, 0, 0, 0.5)'; 
context.fillRect(20, 20, 150, 100); 
下面的测试显示了绘制同一场景使用何不使用阴影效果所带来的显著的性能差异(jsperf):

7.KNOW VARIOUS WAYS TO CLEAR THE CANVAS

因为HTML5 canvas 是一种即时模式(immediate mode)的绘图范式(drawing paradigm),因此场景在每一帧都必需重绘。正因为此,清楚canvas的操作对于 HTML5 应用或者游戏来说有着根本的重要性。

正如在 避免 canvas 状态变化的一节中提到的,清楚整个canvas的操作往往是不可取的。如果你必须这样做的话有两种方法可供选择:调用

context.clearRect(0, 0, width, height) 
或者使用 canvas特定的一个技巧

canvas.width = canvas.width 
在书写本文的时候,cleaRect方法普遍优越于重置canvas宽度的方法。但是,在某些情况下,在Chrome14中使用重置canvas宽度的技巧要比clearRect方法快很多(jsperf):

请谨慎使用这一技巧,因为它很大程度上依赖于底层的canvas实现,因此很容易发生变化,欲了解更多信息请参见 Simon Sarris 的关于清除画布的文章。

 

8.AVOID FLOATING POINT COORDINATES

HTML5 canvas 支持子像素渲染(sub-pixel rendering),而且没有办法关闭这一功能。如果你绘制非整数坐标他会自动使用抗锯齿失真以使边缘平滑。以下是相应的视觉效果(参见Seb Lee-Delisle的关于子像素画布性能的文章)

如果平滑的精灵并非您期望的效果,那么使用 Math.floor方法或者Math.round方法将你的浮点坐标转换成整数坐标将大大提高运行速度(jsperf):

为使浮点坐标抓换为整数坐标你可以使用许多聪明的技巧,其中性能最优越的方法莫过于将数值加0.5然后对所得结果进行移位运算以消除小数部分。

// With a bitwise or. 
rounded = (0.5 + somenum) | 0; 
// A double bitwise not. 
rounded = ~~ (0.5 + somenum); 
// Finally, a left bitwise shift. 
rounded = (0.5 + somenum) << 0; 
两种方法性能对比如下(jsperf):

9.OPTIMIZE YOUR ANIMATIONS WITH ‘REQUESTANIMATIONFRAME’

相对较新的 requeatAnimationFrame API是在浏览器中实现交互式应用的推荐标准。与传统的以固定频率命令浏览器进行渲染不同,该方法可以更友善的对待浏览器,它会在浏览器可用的时候使其来 渲染。这样带来的另外一个好处是当页面不可见的时候,它会很聪明的停止渲染。

requestAnimationFrame调用的目标是以60帧每秒的速度来调用,但是他并不能保证做到。所以你要跟踪从上一次调用导线在共花了多长时间。这看起来可能如下所示:

var x = 100; 
var y = 100; 
var lastRender = new Date(); 
function render() { 
  var delta = new Date() - lastRender; 
  x += delta; 
  y += delta; 
  context.fillRect(x, y, W, H); 
  requestAnimationFrame(render); 

render(); 
注意requestAnimationFrame不仅仅适用于canvas 还适用于诸如WebGL的渲染技术。

在书写本文时,这个API仅仅适用于Chrome,Safari以及Firefox,所以你应该使用这一代码片段

MOST MOBILE CANVAS IMPLEMENTATION ARE SLOW

让我们来讨论一下移动平台。不幸的是在写这篇文章的时候,只有IOS 5.0beta 上运行的Safari1.5拥有GPU加速的移动平台canvas实现。如果没有GPU加速,移动平台的浏览器一般没有足够强大的CPU来处理基于 canvas的应用。上述的JSperf测试用例在移动平台的运行结果要比桌面型平台的结果糟糕很多。这极大的限制了跨设备类应用的成功运行。
CONCLUSION

j简要的讲,本文较全面的描述了各种十分有用优化方法以帮助开发者开发住性能优越的基于HTML5 canvas的项目。你已经学会了一些新的东西,赶紧去优化你那令人敬畏的创造吧!如果你还没有创建过一个应用或者游戏,那么请到Chrome Experiment 和Creative JS看看吧,这里能够激发你的灵感

时间: 2024-10-27 09:31:28

HTML5 Canvas性能分析与提升技巧的相关文章

js HTML5 Canvas绘制转盘抽奖_javascript技巧

本文实例为大家分享了js转盘抽奖的具体代码,供大家参考,具体内容如下 1.实现的基本效果   2.主要的内容  •html5中canvas标签的使用  •jQueryRotate.js旋转插件 3.主要html代码 <body> <div class="content"> <div class="wheel"> <canvas class="item" id="wheelCanvas"

JavaScript html5 canvas绘制时钟效果_javascript技巧

本文实例讲述了JavaScript+html5 canvas绘制时钟效果.分享给大家供大家参考,具体如下:  HTML部分: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.

javascript+HTML5 Canvas绘制转盘抽奖_javascript技巧

之前做过的项目中,有需要抽奖转盘功能的.项目已经完工一段时间了,也没出现什么严重的bug,所以现在拎出来分享给大家. 功能需求1.转盘要美观,转动效果流畅. 2.转盘上需要显示奖品图片,并且奖品是后台读取的照片和名字. 3.转动动画完成后要有相应提示. 4.获取的奖品具体算法在数据库里操作,前端只提供最后的效果展示.  知识要点 1.引用了一个jq插件:awardRotate,用来实现更智能化的转动(插件下载:http://www.jqcool.net/jquery-jqueryrotate.h

详解JS几种变量交换方式以及性能分析对比_javascript技巧

前言 "两个变量之间的值得交换",这是一个经典的话题,现在也有了很多的成熟解决方案,本文主要是列举几种常用的方案,进行大量计算并分析对比. 起由 最近做某个项目时,其中有一个需求是交换数组中的两个元素.当时使用的方法是: arr = [item0,item1,...,itemN]; //最初使用这段代码来交换第0个和第K(k<N)个元素 arr[0] = arr.splice(k, 1, arr[0])[0]; 当时觉得这种方法很优雅,高逼格... 后来,业余时间又拿这个研究下了

asp.ent(C#)中判断空字符串的3种方法以及性能分析_实用技巧

3种方法分别是: string a=""; 1.if(a=="") 2.if(a==String.Empty) 3.if(a.Length==0) 3种方法都是等效的,那么究竟那一种方法性能最高呢?本人用实验说明问题. 建立3个aspx页面(为什么用网页,主要是利用Microsoft Application Center Test ) WebForm1.aspx 复制代码 代码如下: private void Page_Load(object sender, Sys

PHP 性能分析与实验——性能的宏观分析(1)

[编者按]此前,阅读过了很多关于 PHP 性能分析的文章,不过写的都是一条一条的规则,而且,这些规则并没有上下文,也没有明确的实验来体现出这些规则的优势,同时讨论的也侧重于一些语法要点.本文就改变 PHP 性能分析的角度,并通过实例来分析出 PHP 的性能方面需要注意和改进的点. 对 PHP 性能的分析,我们从两个层面着手,把这篇文章也分成了两个部分,一个是宏观层面,所谓宏观层面,就是 PHP 语言本身和环境层面,一个是应用层面,就是语法和使用规则的层面,不过不仅探讨规则,更辅助以示例的分析.

PHP 性能分析与实验:性能的宏观分析

[编者按]此前,阅读过了很多关于 PHP 性能分析的文章,不过写的都是一条一条的规则,而且,这些规则并没有上下文,也没有明确的实验来体现出这些规则的优势,同时讨论的也侧重于一些语法要点.本文就改变 PHP 性能分析的角度,并通过实例来分析出 PHP 的性能方面需要注意和改进的点. 对 PHP 性能的分析,我们从两个层面着手,把这篇文章也分成了两个部分,一个是宏观层面,所谓宏观层面,就是 PHP 语言本身和环境层面,一个是应用层面,就是语法和使用规则的层面,不过不仅探讨规则,更辅助以示例的分析.

开发人员需知:HTML5性能分析面面观

以下这篇文章是由一位名为张黎明的IT技术人员所写,其发表于InfoQ的网页上.这次他在全文里面从9个不同的方面分析HTML5的性能,还是很值得相应的开发人员阅读的. 从性能角度来说,HTML5首先是缩减了HTML文档,使这件事情变得更简单.第一,从用户可读性上说,原先一大堆东西,像初学者第一次看到这些东 西是看不懂的,而HTML5的声明方式对用户来说显然更友好一些. 第二,文档编码的声明,用HTML5方式的话,就很简单.很多人问HTML5是什么?我们说可以先用HTML5的方式就是把DOCTYPE

php性能分析之php-fpm慢执行日志slow log用法浅析_php技巧

本文实例讲述了php性能分析之php-fpm慢执行日志slow log用法.分享给大家供大家参考,具体如下: 众所周知,mysql有slow query log,根据慢查询日志,我们可以知道那些sql语句有性能问题.作为mysql的好搭档,php也有这样的功能.如果你使用php-fpm来管理php的话,你可以通过如下选项开启. PHP 5.3.3 之前设置如下: <value name="request_slowlog_timeout">5s</value> &