简介

HTML5 canvas 最初起源于苹果(Apple)的一项实验,现在已经成为了web中受到广泛支持的2D快速模式绘图(2Dimmediate mode graphic)的标准。许多开发者现在利用它来实现众多的多媒体项目、可视化醒目以及游戏等等。然而,随着我们构建的应用程序的复杂度的增加,我们难免会遇到所谓的性能问题。

已经存在众多优化canvas性能的方法了,但是还没有一篇文章将这些方法系统的整理并加以分析。本文的目的就在于将这些方法整理、巩固以使其曾为 开发者们更容易理解、消化、吸收的资源。本文囊括了适用于所有计算机绘图环境(computer graphics environments)的最基本的优化方法,以及特定于canvas的优化方法。其中特定于canvas的优化方法可能会随着canvas实现方式的 更新而发生变化。特别的,当浏览器开发商实现了canvas GPU 加速时,我们探讨的某些优化方法可能会显得并不是特别有效,这些情况我们会在特定的地方标注出来。

请注意,本文侧重点不在于讨论HTML5 canvas的用法。如果想了解canvas的具体用法可以参见HTML5 Rocks网站中canvas相关的文章。比如Dive into HTML5 chapter 以及MDN tutorial

性能测试

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

浏览者可以在自己的浏览器中打开JSPerf 性能测试页面,而且可以让JSPerf把标准化的测试结果存储在Browserscopebroserscope.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。

没有预渲染的情况:

  1. // canvas, context are defined
  2. function render() {
  3. drawMario(context);
  4. requestAnimationFrame(render);
  5. }

预渲染的情况:

  1. var m_canvas = document.createElement('canvas');
  2. m_canvas.width = 64;
  3. m_canvas.height = 64;
  4. var m_context = m_canvas.getContext(‘2d’);
  5. drawMario(m_context);
  6. function render() {
  7. context.drawImage(m_canvas, 0, 0);
  8. requestAnimationFrame(render);
  9. }

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

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

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

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

  1. can2.width = 100;
  2. can2.height = 40;

如下宽松的canvas将导致糟糕的性能:

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

 

2.BATCH CANVAS CALLS TOGETHER

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

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

  1. or (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. }

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

  1. ontext.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();

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

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

 

3.AVOID UNNECESSARY CANVAS STATE CHANGES

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

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

  1. for (var i = 0; i < STRIPES; i++) {
  2. context.fillStyle = (i % 2 ? COLOR1 : COLOR2);
  3. context.fillRect(i * GAP, 0, GAP, 480);
  4. }

也可以先用一种颜色渲染所有的偶数线条再用另外一种染色渲染所有的基数线条:

  1. context.fillStyle = COLOR1;
  2. for (var i = 0; i < STRIPES/2; i++) {
  3. context.fillRect((i*2) * GAP, 0, GAP, 480);
  4. }
  5. context.fillStyle = COLOR2;
  6. for (var i = 0; i < STRIPES/2; i++) {
  7. context.fillRect((i*2+1) * GAP, 0, GAP, 480);
  8. }

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

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

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

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

  1. context.fillRect(0, 0, canvas.width, canvas.height);

跟踪已绘制部分的边界框,仅仅清理这个边界之内的东西:

  1. 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一个在另一个的上边:

  1. <canvas id="bg" width="640" height="480" style="position: absolute; z-index: 0">
  2. </canvas>
  3. <canvas id="fg" width="640" height="480" style="position: absolute; z-index: 1">
  4. </canvas>

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

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

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

6.AVOID SHADOWBLUR

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

  1. context.shadowOffsetX = 5;
  2. context.shadowOffsetY = 5;
  3. context.shadowBlur = 4;
  4. context.shadowColor = 'rgba(255, 0, 0, 0.5)';
  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的操作往往是不可取的。如果你必须这样做的话有两种方法可供选择:调用

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

或者使用 canvas特定的一个技巧

  1. 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然后对所得结果进行移位运算以消除小数部分。

  1. // With a bitwise or.
  2. rounded = (0.5 + somenum) | 0;
  3. // A double bitwise not.
  4. rounded = ~~ (0.5 + somenum);
  5. // Finally, a left bitwise shift.
  6. rounded = (0.5 + somenum) << 0;

两种方法性能对比如下(jsperf):

9.OPTIMIZE YOUR ANIMATIONS WITH ‘REQUESTANIMATIONFRAME’

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

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

  1. var x = 100;
  2. var y = 100;
  3. var lastRender = new Date();
  4. function render() {
  5. var delta = new Date() - lastRender;
  6. x += delta;
  7. y += delta;
  8. context.fillRect(x, y, W, H);
  9. requestAnimationFrame(render);
  10. }
  11. 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看看吧,这里能够激发你的灵感。

 

REFFERENCE

(原文链接:http://www.html5rocks.com/en/tutorials/canvas/performance/)

提高HTML5 canvas性能的几种方法的更多相关文章

  1. 提高HTML5 Canvas性能的技巧

    详细内容请点击 一:使用缓存技术实现预绘制,减少重复绘制Canvs内容 很多时候我们在Canvas上绘制与更新,总是会保留一些不变的内容,对于这些内容 应该预先绘制缓存,而不是每次刷新. 直接绘制代码 ...

  2. 性能调优之提高 ASP.NET Web 应用性能的 24 种方法和技巧

    性能调优之提高 ASP.NET Web 应用性能的 24 种方法和技巧   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对 ...

  3. 提升PHP性能的21种方法

    提升PHP性能的21种方法. 1.用单引号来包含字符串要比双引号来包含字符串更快一些.因为PHP会在双引号包围的字符串中搜寻变量,单引号则不会.2.如果能将类的方法定义成static,就尽量定义成st ...

  4. 提高SQL查询效率的30种方法

    转载:提高SQL查询效率的30种方法 内容摘录如下: 1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中 ...

  5. .NET开发人员必看:提高ASP.NET Web应用性能的24种方法和技巧

    那性能问题到底该如何解决?以下是应用系统发布前,作为 .NET 开发人员需要检查的点. 1.debug=「false」 当创建 ASP.NET Web应用程序,默认设置为「true」.开发过程中,设置 ...

  6. 提高 ASP.NET Web 应用性能的 24 种方法和技巧(转载)

    在这篇文章中,将介绍一些提高 ASP.NET Web 应用性能的方法和技巧.众所周知,解决性能问题是一项繁琐的工作,当出现性能问题,每个人都会归咎于编写代码的开发人员. 以下为译文 那性能问题到底该如 ...

  7. 提高ASP.NET Web应用性能的24种方法和技巧

    那性能问题到底该如何解决?以下是应用系统发布前,作为 .NET 开发人员需要检查的点. 1.debug=「false」 当创建 ASP.NET Web应用程序,默认设置为「true」.开发过程中,设置 ...

  8. 优化MySQL性能的几种方法-总结

    原文:http://bbs.landingbj.com/t-0-245601-1.html 1.要选取最适用的字段属性 MySQL可以很好的支持大数据量的存取,但是一般说来,数据库中的表越 小,在它上 ...

  9. 提高CSS文件可维护性的五种方法

    当完成一项前端的工作之后,许多人都会忘记该项目的结构与细节.然而代码并不是马上就能完全定型,在余下的时间里还有不断的维护工作,而这些工作也许不会是你自己完成.所以,结构优良的代码能很大程度上优化它的可 ...

随机推荐

  1. LeetCode Database: Consecutive Numbers

    Consecutive Numbers Write a SQL query to find all numbers that appear at least three times consecuti ...

  2. 黑马程序员——经典C语言程序设计100例

    1.数字排列 2.奖金分配问题 3.已知条件求解整数 4.输入日期判断第几天 5.输入整数进行排序 6.用*号显示字母C的图案 7.显示特殊图案 8.打印九九口诀 9.输出国际象棋棋盘 10.打印楼梯 ...

  3. filter在CSS中的效果

    滤镜说明: Alpha:设置透明层次 blur:创建高速度移动效果,即模糊效果 Chroma:制作专用颜色透明 DropShadow:创建对象的固定影子 FlipH:创建水平镜像图片 FlipV:创建 ...

  4. Web服务器与Servlet容器

    今日要闻: Oracle启动了JRE7到JRE8的自动更新, JRE8发布于2014.3,于2014.10成为java.com默认版本, JRE7发布于2011.7, Oracle指定的Java生命政 ...

  5. 实体框架 (EF) 入门 => 二、在全新的数据库中使用 Code First

    学习资料:http://msdn.microsoft.com/zh-cn/data/jj193542 视频建立的控制台应用程序,我在这里使用MVC. 一.非常有磁性的,非常优雅 很喜欢看这个人的视频, ...

  6. POJ 2888 Magic Bracelet(Burnside引理,矩阵优化)

    Magic Bracelet Time Limit: 2000MS   Memory Limit: 131072K Total Submissions: 3731   Accepted: 1227 D ...

  7. RingBuffer源代码分析

    看到一篇写的非常详细的帖子,为防止楼主删帖后找不到,果断转载过来 RingBuffer源代码分析 出处:http://bbs.ickey.cn/community/forum.php?mod=view ...

  8. Cocos2d-x 关于在iOS平台真机测试的一些注意

    下面简单记录一下在最近cocos2d-x项目在iOS平台真机测试和模拟器测试中遇到的一些要注意的地方(使用ipod): 1.图片大小 游戏中基本上都是会用到图片,那么在使用图片的时候要特别注意图片的s ...

  9. LA4329 Ping pong(树状数组与组合原理)

    N (3N20000)ping pong players live along a west-east street(consider the street as a line segment). E ...

  10. C#取得当前目录 转载

    /获取包含清单的已加载文件的路径或 UNC 位置.         public static string sApplicationPath = Assembly.GetExecutingAssem ...