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

时间:2011-11-20 23:56:59

标签: javascript performance optimization canvas

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

请在此主题中添加您所了解,已经学习或在线阅读任何和所有Canvas最佳实践,性能提示/技巧

Canvas仍然是互联网的新手,并且没有任何迹象表明我将来可以看到它变老,没有太多记录在案的"最佳实践"或其他非常重要的提示,必须知道'在任何一个特定的地方与它一起发展。像这样的事情散落在不太知名的地方,很多次。

人们需要知道的事情太多了,而且还要学到很多东西。


我想分享一些东西,以帮助那些正在学习Canvas的人,也许还有一些已经非常了解Canvas的人,并希望从其他人那里得到一些关于他们认为最佳做法或其他提示和技巧的反馈。 HTML5中的画布。

我想从一个我个人认为对开发人员来说非常有用但非常罕见的事情开始。

1。缩进代码

就像你在任何其他时间一样,无论情况如何,用任何其他语言。它是其他一切的最佳实践,我发现在复杂的画布应用程序中,在处理几个不同的上下文和保存/恢复状态时,事情会变得有些混乱。更不用说代码更具可读性和整体清晰度。

例如:

...
// 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声明?你能看到我在这里说的话吗?我认为这应该是开发人员应该继续练习的方法,就像编写简单的javascript或任何其他语言一样。

使用requestAnimationFrame而不是setInterval / setTimeout

setInterval和setTimeout从不打算用作动画计时器,它们只是在一段时间延迟后调用函数的通用方法。如果您将来设置间隔为20毫秒,但您的功能队列需要的时间比执行的时间长,那么您的计时器将在这些功能完成之后才会启动。这可能是一段时间,这在动画方面并不理想。 RequestAnimationFrame 是一种告诉浏览器正在播放动画的方法,因此可以相应地优化重新绘制。它还会限制非活动标签的动画,因此如果您在后台打开它,它就不会杀死您的移动设备的电池。

Nicholas Zakas在他的博客上写了一篇非常详细且内容丰富的article about requestAnimationFrame,非常值得一读。如果你想要一些硬性和快速的实现指令,那么Paul Irish has written a requestAnimationFrame shim - 这就是我在最近的每一个Canvas应用程序中使用的。

ACTUALLY

甚至比使用requestAnimationFrame代替setTimeout和setInterval更好,Joe Lambert编写了一个名为requestInterval和requestTimeout的NEW and improved shim,他解释了使用requestAnimFrame时存在的问题。 您可以查看gist of the script

ACTUALLY x2

既然所有的浏览器已经赶上了这个规范,那么就有update to the requestAnimFrame() polyfill,其中一个可能仍然是用来覆盖所有供应商的。

使用多个画布

@nicolahibbertpost of hers on optimizing Canvas games中写到的动画重型游戏技术提到,使用多个相互叠加的画布可能会更好,而不是在单个画布中完成所有操作。 Nicola解释说&#34;同时在同一个画布上绘制太多像素会导致你的帧率掉落到地板上。以Breakout为例。试图绘制砖块,球,桨,任何电源或武器,然后是背景中的每颗星 - 这根本不会起作用,依次执行这些指令需要很长时间。通过将星空和游戏的其余部分分割成单独的画布,您可以确保合适的帧速率。&#34;

在屏幕外渲染元素

我必须针对包括Samsung's Olympic Genome Project facebook app在内的一些应用程序执行此操作。知道并利用它是否需要是一件非常有用的事情。它极大地减少了加载时间,而且它可以是一种非常有用的技术,可以将图像加载到屏幕之外,因为它们有时会花费一些时间。

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在加载后设置。这是记住要做的关键事情。一旦图像完成加载并绘制到这些临时画布中,您就可以使用相同的ctx.drawImage()将它们绘制到主画布,但不是将图像作为第一个参数,而是使用&t; tmpCtx .canvas&#39;引用临时画布。

其他提示,技巧和资源

Canvas有一个反向引用

2d上下文具有对它的相关DOM元素的反向引用:

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

我希望能听到其他人的更多信息。我正在制作一份我们应该标准化的内容列表,以便为我的公司Front-end Code Standards and Best Practices添加新的部分。我很乐意尽可能多地获得这方面的反馈。

4 个答案:

答案 0 :(得分:16)

重绘区域

动画的最佳画布优化技术是限制每帧上清除/绘制的像素数量。最简单的实现方法是重置整个canvas元素并重新绘制所有内容,但这对浏览器来说是一项昂贵的操作。

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

程序精灵

通过程序生成图形通常是可行的方法,但有时候这不是最有效的方法。如果您使用实心填充绘制简单形状,则以程序方式绘制它们是最好的方法。但是如果你用笔画,渐变填充和其他性能敏感的化妆来绘制更详细的实体,你最好使用图像精灵。

两种方式都可以侥幸成功。在应用程序启动时,在画布上以程序方式绘制图形实体。之后,您可以通过绘制它们的副本来重复使用相同的精灵,而不是重复生成相同的阴影,渐变和笔触。

State Stack&amp;转化

可以通过旋转和缩放等变换操纵画布,从而导致画布坐标系发生变化。这是了解两种方法可用的状态堆栈的重要之处:context.save()(将当前状态推送到堆栈)和context.restore()(恢复到先前的状态)。这使您可以将转换应用于绘图,然后还原回以前的状态,以确保下一个形状不受任何早期转换的影响。状态还包括填充和描边颜色等属性。

合成

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

的抗混叠

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

要解决此问题,您需要舍入到整数值或偏移半个像素,具体取决于您是否正在绘制填充或描边。

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

如果在Canvas元素上调用drawImage,如果将x和y位置四舍五入为整数,则速度会快得多。

Here's a test case on jsperf显示使用整数的速度与使用小数相比更快。

在渲染之前将x和y位置四舍五入为整数。

比Math.round()

更快

Another jsperf test shows Math.round()不一定是舍入数字的最快方法。使用按位破解实际上比内置方法更快。

Canvas Sprite Optimization

清除画布

要清除任何现有像素context.clearRect(x,y,w,h)的整个画布,通常会使用 - 但还有另一个选项可用。每当设置画布的宽度/高度时,即使它们重复设置为相同的值,也会重置画布。在使用动态大小的画布时,这很有用,因为您会注意到图形消失。

计算分布

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

Optimization techniques

答案 1 :(得分:2)

以下是我的提示

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

2)如果您在画布上使用鼠标事件,请使用以下功能,它是可靠的,适用于大多数情况。

/**  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)不要清除整个画布,而只清除需要清洁的部分。它对性能有好处。

答案 2 :(得分:2)

在使用recently launched Facebook app that uses Canvas和用户Facebook个人资料信息(它必须容纳的数据量对于某些人来说是巨大的)之后,还可以使用该应用程序将您和您的朋友与6度等奥运会运动员相匹配在分离类型的东西中,我已经学到了很多东西,我已经学会了尽可能多地尝试提高应用程序内的性能。

我实际上花费了数月和数天,只是努力重新考虑我已经熟悉的代码,并认为这是最佳的做事方式。

尽可能使用DOM元素

事实上,浏览器仍然没有准备好在Canvas中处理更密集的运行应用程序,特别是如果您需要开发支持IE 8的应用程序。有时候DOM比当前更快在编写本文时,Canvas API的实现。 至少我发现它是在制作一个大型复杂的单页面为三星动画html5和画布应用程序。

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

在发布之前的几天,我们决定尝试不同的技术,而不是在屏幕上创建临时画布,这些画布一旦被裁剪成圆圈等就被放置在可见画布上。我们只是在画布上附加了Image DOM元素,使用我们之前用于放置临时画布的x和y坐标。

为了将图像裁剪成圆形,很简单,我们只使用了CSS3 border-radius属性来完成它,这比复杂的状态变化系列要少得多,而且巧妙而富有创意但又过度使用了.clip()方法。

将它们放入DOM后,会发生图像动画,并将每个图像的DOM节点设置为Canvas的独立实体。我们可以通过CSS轻松完全控制样式。

这种技术类似于另一种做这种工作的方法,这种方法也很容易知道,其中包括将画布分层放在彼此之上,而不是将它们绘制到一个上下文中。

答案 3 :(得分:1)

这是我昨晚在列表中提出的更多提示和建议,值得分享。

  • 除非您只需选择<canvas>,否则不要包含jQuery。

    我已经设法在没有它的情况下完成了我在画布上制作的几乎所有内容

  • Create abstracted functions 解密您的代码。从外观或初始绘制状态分离功能。

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

  • 在有意义时使用单字母和双字母变量名称 x,y,z )。

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

    我建议你输入或缩写。单词( dirX,accelX,velX )或者是描述性的,否则以后事情可能会让你感到困惑,相信我。

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

    我制作的Ball构造函数示例:

    // 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);
  • 使用的一个好基础是创建3个函数:     init() - 完成所有初始工作,并设置基础变量和事件处理程序等...     draw() - 调用一次开始游戏并绘制游戏的第一帧,包括创建可能正在改变或需要构建的元素。     update() - 在draw()结束时调用,并通过requestAnimFrame调用。更新更改元素的属性,只执行您需要执行的操作。

  • 在循环中完成最少量的工作,对更改的部分或元素进行更新。创建游戏元素在动画循环之外执行任何其他UI工作。

    动画循环通常是递归函数,这意味着它在动画期间快速重复地调用自己来绘制每个帧。

    如果有多个元素一次动画,你可能想先使用构造函数创建元素,如果你还没有,然后在构造函数中创建一个'timer'方法,使用requestAnimFrame / setTimeout只是如何你通常会在任何动画循环中,但只特别影响这个元素。

    您可以让每个游戏元素在构造函数中拥有自己的计时器,绘制和动画方法

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

或另一种选择:

  • 创建一个Timer()构造函数,您可以使用它并分别为每个动画元素提供动画,从而最大限度地减少动画循环中的工作负荷