HTML5 Canvas 性能和优化技巧、技巧和编码最佳实践 [英] HTML5 Canvas Performance and Optimization Tips, Tricks and Coding Best Practices

查看:24
本文介绍了HTML5 Canvas 性能和优化技巧、技巧和编码最佳实践的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

由于 Canvas 对互联网来说还很陌生,而且我在未来看不到它会变老的迹象,因此没有太多记录在案的最佳实践".或其他非常重要的技巧,这些技巧是在任何特定地方使用它进行开发时必须知道"的.像这样的东西在鲜为人知的网站上多次散布.

With Canvas being still very new to internet, and no signs of it ever getting old that I can see in the future, there are not too many documented "best practices" or other really important tips that are a 'must know' for developing with it in any one particular place. Things like this are scattered around and many times on lesser known sites.

人们需要了解的东西太多了,还有很多东西需要学习.

我想分享一些东西来帮助正在学习 Canvas 的人,也许还有一些已经非常了解它的人,我希望从其他人那里得到一些反馈,让他们知道他们认为使用 Canvas 的最佳实践或其他技巧和窍门HTML5 中的画布.

I wanted to share some things to help people who are learning Canvas and maybe some who already know it quite well and am hoping to get some feedback from others about what they feel are some best practices or other tips and tricks for working with Canvas in HTML5.

我想从我个人认为对开发人员来说非常有用但出奇不常见的事情开始.

I want to start off with one I personally found to be quite a useful yet surprisingly uncommon thing for developers to do.

就像您在其他任何时候一样,无论情况如何,都可以使用任何其他语言.这一直是其他一切的最佳实践,我发现在复杂的画布应用程序中,在处理多个不同的上下文和保存/恢复状态时,事情可能会变得有点混乱.更不用说代码更具可读性,整体看起来也更干净.

Just as you would any other time, in any other language whatever the case may be. It has been a best practice for everything else, and I have come to find that in a complex canvas app, things can get a little confusing when dealing with several different contexts and saved/restore states. Not to mention the code is just more readable and overall cleaner looking too.

例如:

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

与 ELSE 语句相比,IF 语句不是更容易、更清晰地阅读并了解立即发生的事情吗?你能看到我在这里说什么吗?我认为这应该是开发人员应该继续练习的一种方法,就像他们在编写普通的 'ol javascript 或任何其他语言时一样.

Is the IF statement not easier and cleaner to read and know what is what immediately going on than the ELSE statement in this? Can you see what I'm saying here? I think this should be a method that developers should continue to practice just as they would when writing plain 'ol javascript or any other language even.

setInterval 和 setTimeout 从未打算用作动画计时器,它们只是用于在时间延迟后调用函数的通用方法.如果您将来将间隔设置为 20 毫秒,但您的函数队列需要比执行时间更长的时间,则在这些函数完成之前,您的计时器不会触发.这可能需要一段时间,这在动画方面并不理想.RequestAnimationFrame 是一种告诉浏览器正在发生动画的方法,因此它可以相应地优化重绘.它还会限制非活动选项卡的动画,因此如果您将移动设备在后台打开,它不会耗尽您的移动设备的电池.

setInterval and setTimeout were never intended to be used as animation timers, they're just generic methods for calling functions after a time delay. If you set an interval for 20ms in the future, but your queue of functions takes longer than that to execute, your timer won't fire until after these functions have completed. That could be a while, which isn't ideal where animation is concerned. RequestAnimationFrame is a method which tells the browser that an animation is taking place, so it can optimize repaints accordingly. It also throttles the animation for inactive tabs, so it won't kill your mobile device's battery if you leave it open in the background.

Nicholas Zakas 写了一篇非常详细且内容丰富的关于 requestAnimationFrame 的文章 在他的博客上非常值得一读.如果你想要一些硬性和快速的实现说明,那么 Paul Irish 写了一个 requestAnimationFrame shim – 这就是我最近在我制作的每个 Canvas 应用程序中使用的内容.

Nicholas Zakas wrote a hugely detailed and informative article about requestAnimationFrame on his blog which is well worth reading. If you want some hard and fast implementation instructions, then Paul Irish has written a requestAnimationFrame shim – this is what I've used in every one of the Canvas apps I have made until recently.

甚至比使用 requestAnimationFrame 代替 setTimeout 和 setInterval 更好,Joe Lambert 写了一个 新的和改进的 shim 称为 requestInterval 和 requestTimeout,他解释了使用 requestAnimFrame 时存在的问题.您可以查看脚本的要点.

Even better than using requestAnimationFrame in place of setTimeout and setInterval, Joe Lambert has written a NEW and improved shim called requestInterval and requestTimeout, which he explains what issues exist when using requestAnimFrame. You can view the gist of the script.

现在所有的浏览器都已经赶上了这个规范,有一个 对 requestAnimFrame() polyfill 的更新,它可能会继续用于覆盖所有供应商.

Now that all the browsers have caught up on the spec for this, there has been an update to the requestAnimFrame() polyfill, one which will probably remain the one to use to cover all vendors.

一种用于重动画游戏的技术,@nicolahibbert她关于优化 Canvas 游戏的帖子 提到最好使用多个画布叠加在一起而不是这样做一切都在一个画布中.Nicola 解释说,同时在同一个画布上绘制太多像素会导致您的帧率下降.以突围为例.尝试绘制砖块、球、桨、任何电源或武器,然后绘制背景中的每颗星星 - 这根本行不通,依次执行这些指令需要很长时间.通过将星空和游戏的其余部分拆分到单独的画布上,您可以确保获得不错的帧率."

A technique for animation-heavy games which @nicolahibbert wrote about in a post of hers on optimizing Canvas games mentions that it may be better to use multiple canvasses layered on top of one another rather than do everything in a single canvas. Nicola explains that "drawing too many pixels to the same canvas at the same time will cause your frame rate to fall through the floor. Take Breakout for example. Trying to draw the bricks, the ball, the paddle, any power-ups or weapons, and then each star in the background – this simply won't work, it takes too long to execute each of these instructions in turn. By splitting the starfield and the rest of the game onto separate canvases, you are able to ensure a decent framerate."

我不得不为我制作的一些应用程序执行此操作,包括三星的奥林匹克基因组计划 Facebook 应用程序.了解并利用它是否需要,这是一件非常有用的事情.它极大地减少了加载时间,而且它可以是一种非常有用的技术,可以将图像加载到屏幕外,因为它们有时需要一段时间.

I have had to do this for a few apps I've made including Samsung's Olympic Genome Project facebook app. It's an extremely useful thing to know and to make use of whether it's needed or not. It decreases load time immensely, plus it can be a really useful technique to load images off screen since they can sometimes take a while.

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() 将它们绘制到主画布上,但不是将图像作为第一个参数,而是使用 'tmpCtx.canvas'引用临时画布.

Notice that the src of the image is set after it is loaded. This is a key thing to remember to do too. Once the images are done loading and drawn into these temp canvases, you can then draw them to your main canvas by using the same ctx.drawImage(), but instead of putting the image as the first argument, you use 'tmpCtx.canvas' to reference the temp canvas.

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

The 2d context has a back reference to it's associated DOM element:

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

我很想从其他人那里听到更多关于这方面的信息.我正在制定一份我们应该标准化的清单,以便在我公司的 前端代码标准中添加一个新部分最佳做法.我很想得到尽可能多的反馈.

I'd love to hear more from other people on this. I am working on making a list of things that we should standardize to add a new section to my company's Front-end Code Standards and Best Practices. I'd love to get as much feedback on this as I can.

推荐答案

重绘区域

动画的最佳画布优化技术是限制在每一帧上清除/绘制的像素数量.最容易实现的解决方案是重置整个画布元素并重新绘制所有内容,但这对于您的浏览器来说是一项代价高昂的操作.

Redraw Regions

The best canvas optimization technique for animations is to limit the amount of pixels that get cleared/painted on each frame. The easiest solution to implement is resetting the entire canvas element and drawing everything over again but that is an expensive operation for your browser to process.

在帧之间重复使用尽可能多的像素.这意味着每帧需要处理的像素越少,程序运行得越快.例如,在使用 clearRect(x, y, w, h) 方法擦除像素时,仅清除和重绘发生变化的像素而不是整个画布是非常有益的.

Reuse as many pixels as possible between frames. What that means is the fewer pixels that need to be processed each frame, the faster your program will run. For example, when erasing pixels with the clearRect(x, y, w, h) method, it is very beneficial to clear and redraw only the pixels that have changed and not the full canvas.

按程序生成图形通常是可行的方法,但有时这并不是最有效的方法.如果您要绘制带有实心填充的简单形状,那么按程序绘制它们是最好的方法.但是,如果您使用笔触、渐变填充和其他对性能敏感的构成来绘制更详细的实体,则最好使用图像精灵.

Generating graphics procedurally is often the way to go, but sometimes that's not the most efficient one. If you're drawing simple shapes with solid fills then drawing them procedurally is the best way do so. But if you're drawing more detailed entities with strokes, gradient fills and other performance sensitive make-up you'd be better off using image sprites.

两者兼而有之是可能的.当您的应用程序启动时,在画布上按程序绘制图形实体一次.之后,您可以通过绘制它们的副本来重复使用相同的精灵,而不是重复生成相同的阴影、渐变和笔触.

It is possible to get away with a mix of both. Draw graphical entities procedurally on the canvas once as your application starts up. After that you can reuse the same sprites by painting copies of them instead of generating the same drop-shadow, gradient and strokes repeatedly.

可以通过旋转和缩放等转换来操作画布,从而改变画布坐标系.这是了解状态堆栈的重要之处,有两种方法可用:context.save()(将当前状态推送到堆栈)和 context.restore()(恢复到先前的状态).这使您可以对绘图应用变换,然后恢复到之前的状态,以确保下一个形状不受任何先前变换的影响.状态还包括填充和笔触颜色等属性.

The canvas can be manipulated via transformations such as rotation and scaling, resulting in a change to the canvas coordinate system. This is where it's important to know about the state stack for which two methods are available: context.save() (pushes the current state to the stack) and context.restore() (reverts to the previous state). This enables you to apply transformation to a drawing and then restore back to the previous state to make sure the next shape is not affected by any earlier transformation. The states also include properties such as the fill and stroke colors.

在使用画布时,一个非常强大的工具是合成模式,除其他外,它允许蒙版和分层.有多种可用的复合模式,它们都是通过画布上下文的 globalCompositeOperation 属性设置的.复合模式也是状态堆栈属性的一部分,因此您可以应用复合操作,堆叠状态并应用不同的状态,然后恢复到您创建第一个状态之前的状态.这可能特别有用.

A very powerful tool at hand when working with canvas is compositing modes which, amongst other things, allow for masking and layering. There's a wide array of available composite modes and they are all set through the canvas context's globalCompositeOperation property. The composite modes are also part of the state stack properties, so you can apply a composite operation, stack the state and apply a different one, and restore back to the state before where you made the first one. This can be especially useful.

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

To allow for sub-pixel drawings, all browser implementations of canvas employ anti-aliasing (although this does not seem to be a requirement in the HTML5 spec). Anti-aliasing can be important to keep in mind if you want to draw crisp lines and notice the result looks blurred. This occurs because the browser will interpolate the image as though it was actually between those pixels. It results in a much smoother animation (you can genuinely move at half a pixel per update) but it'll make your images appear fuzzy.

要解决此问题,您需要四舍五入为整数值或偏移半个像素,具体取决于您是绘制填充还是描边.

To work around this you will need to either round to whole integer values or offset by half a pixel depending on if you're drawing fills or strokes.

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

If you call drawImage on the Canvas element, it's much faster if you round the x and y position to a whole number.

这是一个关于 jsperf 的测试案例 显示使用整数比使用小数快多少.

Here's a test case on jsperf showing how much faster using whole numbers is compared to using decimals.

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

So round your x and y position to whole numbers before rendering.

另一个 jsperf 测试表明 Math.round() 不一定是四舍五入的最快方法.事实证明,使用按位 hack 实际上比内置方法更快.

Another jsperf test shows that Math.round() is not necessarily the fastest method for rounding numbers. Using a bitwise hack actually turns out to be faster than the built in method.

画布精灵优化

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

To clear the entire canvas of any existing pixels context.clearRect(x, y, w, h) is typically used – but there is another option available. Whenever the width/height of the canvas are set, even if they are set to the same value repeatedly, the canvas is reset. This is good to know when working with a dynamically sized canvas as you will notice drawings disappearing.

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

The Chrome Developer Tools profiler is very useful for finding out what your performance bottlenecks are. Depending on your application you may need to refactor some parts of your program to improve the performance and how browsers handle specific parts of your code.

优化技巧

这篇关于HTML5 Canvas 性能和优化技巧、技巧和编码最佳实践的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆