Documente Academic
Documente Profesional
Documente Cultură
http://www.html5rocks.com/en/tutorials/canvas/p...
Introduction
HTML5 canvas, which started as an experiment from Apple, is the most widely
supported standard for 2D immediate mode graphics on the web. Many developers now
rely on it for a wide variety of multimedia projects, visualizations, and games. However,
as the applications we build increase in complexity, developers inadvertently hit the
performance wall.
Theres a lot of disconnected wisdom about optimizing canvas performance. This article
aims to consolidate some of this body into a more readily digestible resource for
developers. This article includes fundamental optimizations that apply to all computer
graphics environments as well as canvas-specific techniques that are subject to change
as canvas implementations improve. In particular, as browser vendors implement
canvas GPU acceleration, some of the outlined performance techniques discussed will
likely become less impactful. This will be noted where appropriate.
Note that this article does not go into usage of HTML5 canvas. For that, check out
these canvas related articles on HTML5Rocks, this chapter on the Dive into HTML5 site
or the MDN Canvas tutorial.
Performance testing
To address the quickly changing world of HTML5 canvas, JSPerf (jsperf.com) tests
verify that every proposed optimization still works. JSPerf is a web application that
allows developers to write JavaScript performance tests. Each test focuses on a result
that youre trying to achieve (for example, clearing the canvas), and includes multiple
approaches that achieve the same result. JSPerf runs each approach as many times as
possible over a short time period and gives a statistically meaningful number of
iterations per second. Higher scores are always better!
Visitors to a JSPerf performance test page can run the test on their browser, and let
JSPerf store the normalized test results on Browserscope (browserscope.org). Because
the optimization techniques in this article are backed up by a JSPerf result, you can
return to see up-to-date information about whether or not the technique still applies. Ive
written a small helper application that renders these results as graphs, embedded
throughout this article.
All of the performance results in this article are keyed on the browser version. This
turns out to be a limitation, since we don't know what OS the browser was running on,
1 de 12
06/04/15 17:10
http://www.html5rocks.com/en/tutorials/canvas/p...
or even more importantly, whether or not HTML5 canvas was hardware accelerated
when the performance test ran. You can find out if Chrome's HTML5 canvas is
hardware accelerated by visiting about:gpu in the address bar.
pre-rendering:
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);
}
2 de 12
06/04/15 17:10
All
Modern Only
http://www.html5rocks.com/en/tutorials/canvas/p...
Mobile Only
None
This technique is especially effective when the rendering operation (drawMario in the
above example) is expensive. A good example of this is text rendering, which is a very
expensive operation. Here is the sort of dramatic performance boost you can expect
from pre-rendering this operation (from this jsperf):
All
Modern Only
Mobile Only
None
However, observe that in the above example, the poor performance of the
pre-rendered loose test case. When pre-rendering, its important to make sure that
your temporary canvas fits snugly around the image you are drawing, otherwise the
performance gain of off-screen rendering is counterweighted by the performance loss of
copying one large canvas onto another (which varies as a function of source target
size). A snug canvas in the above test is simply smaller:
can2.width = 100;
can2.height = 40;
3 de 12
06/04/15 17:10
http://www.html5rocks.com/en/tutorials/canvas/p...
This applies to the world of HTML5 canvas as well. When drawing a complex path, for
example, its better to put all of the points into the path, rather than rendering the
segments separately (jsperf).
All
Modern Only
Mobile Only
None
Note, however, that with Canvas, theres an important exception to this rule: if the
primitives involved in drawing the desired object have small bounding boxes (for
example, horizontal and vertical lines), it may actually be more efficient to render them
separately (jsperf):
4 de 12
06/04/15 17:10
All
Modern Only
http://www.html5rocks.com/en/tutorials/canvas/p...
Mobile Only
None
The following performance test draws an interlaced pinstripe pattern using the two
approaches (jsperf):
5 de 12
06/04/15 17:10
All
Modern Only
http://www.html5rocks.com/en/tutorials/canvas/p...
Mobile Only
None
As expected, the interlaced approach is slower because changing the state machine is
expensive.
Keep track of the drawn bounding box, and only clear that.
context.fillRect(last.x, last.y, last.width, last.height);
This is illustrated in the following performance test which involves a white dot crossing
the screen (jsperf):
All
6 de 12
Modern Only
Mobile Only
None
06/04/15 17:10
http://www.html5rocks.com/en/tutorials/canvas/p...
If you are familiar with computer graphics, you might also know this technique as
redraw regions, where the previously rendered bounding box is saved, and then
cleared on each rendering.
This technique also applies to pixel-based rendering contexts, as is illustrated by this
JavaScript Nintendo emulator talk.
The advantage over having just one canvas here, is that when we draw or clear the
foreground canvas, we dont ever modify the background. If your game or multimedia
app can be split up into a foreground and background, consider rendering these on
separate canvases to get a significant performance boost. The following graph
compares the naive single canvas case to one where you merely redraw and clear the
foreground (jsperf):
All
Modern Only
Mobile Only
None
You can often take advantage of imperfect human perception and render the
background just once or at a slower speed compared to the foreground (which is likely
to occupy most of your users attention). For example, you can render the foreground
every time you render, but render the background only every Nth frame.
Also note that this approach generalizes well for any number of composite canvases if
your application works better with a this sort of structure.
7 de 12
06/04/15 17:10
http://www.html5rocks.com/en/tutorials/canvas/p...
Avoid shadowBlur
Like many other graphics environments, HTML5 canvas allows developers to blur
primitives, but this operation can be very expensive:
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowBlur = 4;
context.shadowColor = 'rgba(255, 0, 0, 0.5)';
context.fillRect(20, 20, 150, 100);
The following performance test shows the same scene rendered with and without
shadow and the drastic performance difference (jsperf):
All
Modern Only
Mobile Only
None
8 de 12
06/04/15 17:10
All
Modern Only
http://www.html5rocks.com/en/tutorials/canvas/p...
Mobile Only
None
Be careful with this tip, since it depends heavily on the underlying canvas
implementation and is very much subject to change. For more information, see Simon
Sarris' article on clearing the canvas.
If the smoothed sprite is not the effect you seek, it can be much faster to convert your
coordinates to integers using Math.floor or Math.round (jsperf):
9 de 12
06/04/15 17:10
All
Modern Only
http://www.html5rocks.com/en/tutorials/canvas/p...
Mobile Only
None
To convert your floating point coordinates to integers, you can use several clever
techniques, the most performant of which involve adding one half to the target number,
and then performing bitwise operations on the result to eliminate the fractional part.
// 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;
All
Modern Only
Mobile Only
None
Note that this sort of optimization should no longer matter once canvas implementations
are GPU accelerated which will be able to quickly render non-integer coordinates.
10 de 12
06/04/15 17:10
http://www.html5rocks.com/en/tutorials/canvas/p...
rendering routine and get called when the browser is available. As a nice side effect, if
the page is not in the foreground, the browser is smart enough not to render.
The requestAnimationFrame callback aims for a 60 FPS callback rate but doesnt
guarantee it, so you need to keep track of how much time passed since the last render.
This can look something like the following:
var x = 100;
var y = 100;
var lastRender = Date.now();
function render() {
var delta = Date.now() - lastRender;
x += delta;
y += delta;
context.fillRect(x, y, W, H);
requestAnimationFrame(render);
}
render();
Conclusion
To recap, this article covered a comprehensive set of useful optimization techniques
that will help you develop performant HTML5 canvas-based projects. Now that youve
learned something new here, go forth and optimize your awesome creations. Or, if you
dont currently have a game or application to optimize, check out Chrome Experiments
and Creative JS for inspiration.
References
Immediate mode vs. retained mode.
Other HTML5Rocks canvas articles.
The Canvas section of Dive into HTML5.
JSPerf lets developers create JS performance tests.
Browserscope stores browser performance data.
JSPerfView, which renders JSPerf tests as charts.
Simon's blog post on clearing the canvas, and his book, HTML5 Unleashed which
11 de 12
06/04/15 17:10
http://www.html5rocks.com/en/tutorials/canvas/p...
12 de 12
06/04/15 17:10