HTML5 Canvas性能和优化技巧,技巧和编码最佳实践

你知道一些帆布的最佳实践吗?

请添加到这个线程你知道,已经学到,或已经在线阅读了任何和所有的帆布最佳实践,性能提示/技巧

由于canvas对于互联网来说还是一个新的东西,而且没有迹象显示它将来会变老,所以并没有太多记载的“最佳实践”或其他非常重要的提示,这些提示是“必须知道的”它在任何一个特定的地方。 像这样的东西散落在许多不为人知的地方。

人们需要了解的东西太多了,而且还要学习太多东西。


我想分享一些东西来帮助那些正在学习Canvas的人,也许一些人已经很了解它,并希望从别人那里得到一些他们觉得是一些最佳实践的反馈,或者在HTML5中使用Canvas的其他技巧和窍门。

我想从一个我个人发现对开发人员来说相当有用但出人意料的事情开始。

1.缩进你的代码

就像你在其他任何时候一样,用任何其他语言,无论这种情况如何。 对于其他任何事情来说,这都是最佳实践,而且我发现,在复杂的canvas应用程序中,在处理几个不同的上下文和保存/恢复状态时,可能会有些混乱。 更不用说代码只是更可读,整体更清洁。

例如:

... // Try to tell me this doesn't make sense to do ctx.fillStyle = 'red'; ctx.fill(); ctx.save(); if (thing < 3) { // indenting ctx.beginPath(); ctx.arc(2, 6, 11, 0, Math.PI*2, true); ctx.closePath(); ctx.beginPath(); ctx.moveTo(20, 40); ctx.lineTo(10, 200); ctx.moveTo(20, 40); ctx.lineTo(100, 40); ctx.closePath(); ctx.save(); ctx.fillStyle = 'blue' ctx.fill(); ctx.restore(); } else { // no indenting ctx.drawImage(img, 0, 0, 200, 200); ctx.save(); ctx.shadowBlur(); ctx.beginPath(); ctx.arc(2, 60, 10, 0, Math.PI*2, false); ctx.closePath(); ctx.fillStyle 'green'; ctx.fill(); ctx.restore(); } ctx.restore(); ctx.drawRect(); ctx.fill(); ... 

IF语句是不是更容易和更清晰地阅读和知道什么是立即比ELSE声明在这个? 你能看到我在说什么吗? 我认为这应该是一个开发者应该继续实践的方法,就像他们在编写纯粹的ol或其他语言时一样。

使用requestAnimationFrame而不是setInterval / setTimeout

setInterval和setTimeout从来没有打算用作animation计时器,它们只是在一段时间后调用函数的通用方法。 如果将来设置了20ms的时间间隔,但是您的函数队列需要比要执行的更长的时间,那么在这些函数完成之后,您的计时器将不会启动。 这可能是一段时间,这对animation而言并不理想。 RequestAnimationFrame是一种告诉浏览器animation正在发生的方法,因此可以相应地优化重绘。 它还会为非活动选项卡节制animation,因此,如果将其保留在后台,它将不会杀死移动设备的电池。

Nicholas Zakas在他的博客上写了一篇关于requestAnimationFrame的非常详细和翔实的文章 ,值得一读。 如果你想要一些硬性和快速的实现说明,那么Paul Irish写了一个requestAnimationFrame垫片 – 这是我在最近所做的每个Canvas应用程序中使用的。

其实

甚至比使用requestAnimationFrame代替setTimeout和setInterval更好,Joe Lambert已经写了一个名为requestInterval和requestTimeout的NEW和改进的Shim ,他解释了使用requestAnimFrame时存在什么问题。 您可以查看脚本的要点 。

实际上x2

现在所有的浏览器都已经抓住了这个规范, requestAnimFrame()的填充已经有了一个更新,这个更新可能会被用来覆盖所有的供应商。

使用多个canvas

@nicolahibbert在她的一篇关于优化Canvas游戏的文章中写道,animation重型游戏的技术提到,使用多个层叠在一起的canvas可能会更好,而不是在单个canvas上做所有事情。 Nicola解释说:“在同一个canvas上同时画出太多的像素会导致帧率下降,例如以Breakout为例,试图绘制砖块,球,桨,任何通电或武器,然后每个星星在后台 – 这根本行不通,依次执行这些指令需要很长的时间,通过将星空和游戏的其余部分拆分到不同的canvas上,您可以确保体面帧率“。

渲染元素离屏

我必须为我做的几个应用程序做这个,包括三星的奥运基因组计划的Facebook应用程序 。 知道并且利用是否需要是非常有用的。 它极大地减less了加载时间,加上它可以是一个非常有用的技术,因为他们有时可能需要一段时间加载图像。

 var tmpCanvas = document.createElement('canvas'), tmpCtx = tmpCanvas.getContext('2d'), img = document.createElement('img'); img.onload = function() { tmpCtx.drawImage(thumbImg, 0, 0, 200, 200); }; img.src = '/some/image/source.png'; 

请注意,图像的src在加载后被设置。 这也是要记住要做的关键。 一旦图像完成加载并绘制到这些临时canvas中,然后可以使用相同的ctx.drawImage()将它们绘制到主canvas上,而不是将图像作为第一个参数,而是使用“tmpCtx.canvas”引用临时canvas。

其他提示,技巧和资源

  • canvastesting用例
  • 一些更多的canvas和JStesting
  • HTML5Rocks的性能提高
  • ** requestAnimFrame来优化拖动事件

帆布有一个回参考

2d上下文有一个对其相关DOM元素的反向引用:

 var ctx = doc.getElementById('canvas').getContext('2d'); console.log(ctx.canvas); // HTMLCanvasElement 

我很想听到其他人的更多消息。 我正在制定一个我们应该标准化的事项清单,在我的公司的前端代码标准和最佳实践中增加一个新的部分。 我希望尽可能多地获得这方面的反馈意见。

重绘区域

animation的最佳canvas优化技术是限制在每一帧上被清除/绘制的像素数量。 要实现的最简单的解决scheme是重置整个canvas元素,并再次绘制所有内容,但这对浏览器来说是一个昂贵的操作。

在帧之间重复使用尽可能多的像素。 这意味着每帧需要处理的像素越less,程序运行得越快。 例如,使用clearRect(x,y,w,h)方法擦除像素时,清除和重绘仅改变已更改的像素而非完整canvas是非常有益的。

程序精灵

程序生成graphics通常是要走的路,但有时候并不是最有效的。 如果你用简单的填充来绘制简单的graphics,那么程序化绘制它们是最好的方法。 但是,如果你用笔画,渐变填充和其他性能敏感的化妆绘制更详细的实体,你最好使用图像精灵。

有可能混合两种都逃脱。 在应用程序启动时,在canvas上以程序方式绘制graphics实体。 之后,您可以通过复制副本来重复使用相同的精灵,而不是重复生成相同的阴影,渐变和描边。

状态堆栈和转换

canvas可以通过旋转和缩放等转换操作,导致canvas坐标系统发生变化。 这是了解有两种方法可用的状态堆栈的重要信息:context.save()(将当前状态推送到堆栈)和context.restore()(恢复到之前的状态)。 这使您可以将转换应用到graphics,然后恢复到以前的状态,以确保下一个形状不受任何早期转换的影响。 这些州还包括诸如填充和笔画颜色之类的属性。

合成

当使用canvas时,一个非常强大的工具是合成模式,其中包括掩蔽和分层。 有许多可用的复合模式,它们都是通过canvas上下文的globalCompositeOperation属性设置的。 复合模式也是状态堆栈属性的一部分,因此您可以应用复合操作,堆叠状态并应用另一个状态,并恢复到第一个状态之前的状态。 这可能是特别有用的。

抗锯齿

为了允许子像素绘图,所有浏览器的canvas实现都使用反锯齿(尽pipe这在HTML5规范中似乎不是必需的)。 如果要绘制清晰的线条并注意结果看起来模糊,则消除锯齿可能很重要。 发生这种情况是因为浏览器将插入图像,就好像它实际上在这些像素之间。 它会产生更平滑的animation(每更新一次可以真正移动半个像素),但会使图像显得模糊不清。

要解决这个问题,你将需要舍入到整数值或偏移半个像素,取决于你是否绘制填充或笔划。

使用整数为drawImage()x和y位置

如果您在Canvas元素上调用drawImage,则将x和y位置四舍五入为整数更快。

这里是jsperf上的一个testing用例,显示使用整数与使用小数相比要快多less。

所以在渲染之前,将你的x和y的位置舍入到整数。

比Math.round()更快

另一个jsperftesting显示 Math.round()不一定是舍入数字的最快方法。 实际上使用一个按位破解比内置的方法更快。

canvas雪碧优化

清除canvas

要清除任何现有像素的整个canvas,通常使用context.clearRect(x,y,w,h) – 但还有其他选项可用。 无论何时设置canvas的宽度/高度,即使它们被重复设置为相同的值,canvas也会被重置。 在使用dynamic大小的canvas时,这很好理解,因为您会注意到graphics消失。

计算分布

Chrome开发人员工具分析器对于了解您的性能瓶颈是非常有用的。 根据您的应用程序,您可能需要重构程序的某些部分以提高性能以及浏览器如何处理代码的特定部分。

优化技术

在使用最近推出的Facebook应用程序后,使用Canvas和用户的Facebook个人资料信息(它必须容纳的数据量非常大),以匹配你和你的朋友也使用应用程序,奥运选手喜欢6度分离types的东西,有很多我已经学会了我的广泛努力,尽我所能可能尝试提高应用程序内的性能。

我花了几个月的时间,几天的时间只是重新考虑我已经知道的代码,并相信这是做最好的方法。

尽可能使用DOM元素

事实上,浏览器还没有准备好在Canvas中处理更多的运行中的应用程序,特别是如果您需要开发支持IE 8的应用程序时。有时候,DOM的速度比目前的Canvas API在写这个的时候。 至less我发现它是在为三星制作html5和canvas应用程序的大量复杂单页上工作的。

我们能够很好地改善事物的性能,同时仍然使用Canvas来完成一些复杂的工作,将图像裁剪成圆形,这可能是坚持我们如何做的。

在发布之前的几天,我们决定尝试一种不同的技术,而不是在屏幕上创build临时的canvas,一旦裁剪成圆形等等,我们就在canvas上添加了图像DOM元素,使用x和y坐标,我们曾经用来放置温度的canvas。

为了将图像裁剪成圆形,很简单,我们只是使用了CSS3的border-radius属性来完成,这比复杂的一系列状态更改要less得多,而巧妙和创造性却又过度使用了.clip( ) 方法。

一旦将它们放置在DOM中,就会出现图像的animation,并将每个图像的DOM节点作为Canvas的独立实体进行animation。 我们可以通过CSS轻松完全控制样式。

这种技术类似于另一种做这种types的工作的方法,这个方法也是相当不错的,它涉及将Canvases层叠在一起,而不是将它们绘制到一个上下文中。

这里是我的提示

1)使用clearRect清除canvas,而不是canvas.width = canvas.width,因为稍后重置canvas状态

2)如果您在canvas上使用鼠标事件使用以下function,它是可靠的,并在大多数情况下工作。

 /** returns the xy point where the mouse event was occured. @param ev The event object. */ function getXY(ev){ return getMousePosition(ev, ev.srcElement || ev.originalTarget); } /** returns the top-left point of the element @param elem The element */ function getElementPos(elem){ var obj = elem; var top = 0; var left = 0; while (obj && obj.tagName != "BODY") { top += obj.offsetTop-obj.scrollTop; left += obj.offsetLeft -obj.scrollLeft ; obj = obj.offsetParent; } return { top: top, left: left }; }; /** returns the xy point where the mouse event was occured inside an element. @param ev The event object. @param elem The element */ function getMousePosition(evt, elem){ var pageX, pageY; if(typeof(window.pageYOffset)=='number') { pageX=window.pageXOffset; pageY=window.pageYOffset; }else{ pageX=document.documentElement.scrollLeft; pageY=document.documentElement.scrollTop; } var mouseX = evt.clientX - getElementPos(elem).left + pageX; var mouseY = evt.clientY - getElementPos(elem).top + pageY; return { x: mouseX, y: mouseY }; }; 

3)如果你想支持IE7,使用ExplorerCanvas

4)而不是清除整个canvas只清除需要清理的部分。 它对性能有好处。

以下是昨晚我列入清单的更多提示和build议,值得分享。

  • 不要包含jQuery,除非你需要做的不仅仅是select<canvas>

    几乎我在canvas上做的每一件事,我都设法得到它

  • 创build抽象函数解耦你的代码 。 与外观或初始绘制状态分开的function。

    尽可能使可重用的通用function。 理想情况下,您应该使用模块模式,您可以创build一个包含常用函数的utils对象。

  • 使用单字母和双字母variables名称x,y,z是有意义的

    Canvas中的坐标系统添加了更多的通常被声明为variables的单个字母。 这可能会导致创build多个单/双variables( dX,dY,aX,aY,vX,vY )作为元素的一部分。

    我build议你input或缩写。 单词( dirX,accelX,velX )或者是描述性的,否则后面的事情会让你相当困惑,相信我。

  • 创build可以根据需要调用游戏元素的构造函数。 您可以在构造函数中添加自定义方法和属性,并创build您可能需要的任意数量,并且它们都将拥有自己的属性和方法。

    一个球构造函数的例子我做了:

     // Ball constructor var Ball = function(x, y) { this.x = x; this.y = y; this.radius = 10; this.color = '#fff'; // Direction and min, max x,y this.dX = 15; this.dY = -15; this.minX = this.minY = 20 + this.radius; this.maxX = this.radius - (canvasWidth - 20); this.maxY = this.radius + canvasHeight; this.draw = function(ctx) { ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, twoPI, true); ctx.closePath(); ctx.save(); ctx.fillStyle = this.color; ctx.fill(); ctx.restore(); }; }; 

创造球

 ball = new Ball(centerX, canvasHeight - paddle.height - 30); ball.draw(ctx); 
  • 一个好的基础是创build3个函数:init() – 完成所有的初始工作,设置基本variables和事件处理程序etc … draw() – 调用一次开始游戏并绘制第一帧游戏,包括创造可能会改变或需要构build的元素。 update() – 在draw()的结尾处调用,并通过requestAnimFrame在本身内部调用。 更新更改元素的属性,只做你需要在这里做的事情。

  • 在循环中做最less量的工作,对不断变化的部分或元素进行更新 。 创build游戏元素在animation循环之外执行任何其他UI工作。

    animation循环通常是一个recursion函数,意味着在animation绘制每一帧的过程中,它会自动快速地重复调用。

    如果有很多元素同时被animation,你可能需要先使用构造函数创build元素,如果你还没有,然后在构造函数中创build一个具有requestAnimFrame / setTimeout的'timer'方法在任何animation循环中,但只影响这个元素。

    你可以让每个游戏元素都有自己的计时器,绘制和animation构造函数中的方法

    这样做可以让你完全分离每个元素的控制,并且一个大的animation循环根本就不需要,因为循环被分解成每个元素,并且你可以随意启动/停止。

或者另一种select:

  • 创build一个Timer()构造函数 ,您可以单独使用每个animation元素,从而最大限度地减lessanimation循环中的工作负载