Efficient redrawing on SVG - or why suspendRedraw is a lie
More than once we were asked on http://bugs.webkit.org/ to implement the suspendRedraw method from the SVGSVGElement interface into WebKit. What does this method exactly do?
Controlling repainting of the canvas
The method was introduced in SVG 1.1 to control drawing of elements. It takes a time in milliseconds as parameter. The drawing of the element gets suspended for this period of time. This means the element does not get updated during that time. It is not possible to suspend drawing for more than 60 seconds. Every value beyond that limit will be clamped to the 60 seconds.
Reading the bug reports that we got, I found three different kind of use cases where authors might think about suspendRedraw.
1) Avoid redrawing on setting multiple element attributes
Imagine an SVGRectElement
<rect width="50" height="50" fill="green" />
with following changes to the DOM:
rect.setAttribute("x", "100");
rect.setAttribute("y", "200");
rect.setAttribute("width", "300");
rect.setAttribute("height", "400");
In theory the element gets redrawn after every of the four setAttribute calls. Of course it doesn't make sense to redraw the element after every call. So authors may want to control the drawing. This could look like in the following snippet:
var elementId = rect.ownerSVGElement.suspendRedraw(20);
rect.setAttribute("x", "100");
rect.setAttribute("y", "200");
rect.setAttribute("width", "300");
rect.setAttribute("height", "400");
rect.ownerSVGElement.unsuspendRedraw(elementId);
First we get the root element of the rect. The root element is of type SVGSVGElement, which has the method definition for suspendRedraw, unsuspendRedraw and unsuspendRedrawAll. We pass an argument with a value of 20 (which means 20 ms) to this method. The method returns an integer value that is unique and identifies our rect element. Now we set the new values for the width attribute of the element. We have set all values now. No need to suspend the redrawing any longer. So we can unsuspend the drawing again. This is done by the method unsuspendRedraw. It takes the unique identifier that we got from suspend as argument to identify our rect. The root element will unsuspend our rect for redrawing now.
The value that we passed to suspendRedraw doesn't really matter, since we unsuspend the redrawing right after our changes to rect.
Maybe the author sets an attribute multiple times, according to certain conditions. A reduced example could look like this:
var list = new Array(400, 200, 500, 100);
for (var i = 0; i < list.length; i++)
rect.setAttribute("width", list[i]);
Of course you wouldn't want the rectangle to get redrawn list.length times. So you might think about disabling the redrawing functionality for the time you set the attributes.
var elementId = rect.ownerSVGElement.suspendRedraw(20);
var list = new Array(400, 200, 500, 100);
for (var i = 0; i < list.length; i++)
rect.setAttribute("width", list[i]);
rect.ownerSVGElement.unsuspendRedraw(elementId);
Note: You should limit DOM access as much as possible in general to avoid performance problems.
You might think that this is a good idea. It should be done all the time. - Don't do it!
Browsers are quite smart nowadays. In WebKit we realize that the attributes changed, but we just make an internal note of the change and wait till the end of the execution context before we trigger the redrawing. This is true for both examples above and all browsers have these kind of optimizations.
2) Avoid redrawing of invisible content
Sometimes people want to suspend redrawing of content that is not visible on the screen. In one bug report we were asked for suspendRedraw to stop redrawing of invisible elements during scrolling. Let's take a look at the following SVG fragment:
<div style="overflow: auto; width: 640px; height: 480px;">
<svg width="640" height="1440">
<rect x="10" y="700" width="10" height="10" fill="green">
<animate attributeName="width" from="10" to="620" dur="3s"
repeatCount="indefinite" />
</rect>
</svg>
</div>
The SVG fragment has a size of 640 pixel to 1440 pixel. The document includes a small rectangle. The width of the rectangle is animated repeatedly. Let's assume that we have a screen size of 640 pixel width by 480 pixel height (our div box in the example above). The rectangle (positioned at 700 pixel from the top-left corner) would clearly be invisible for us. An author might want to suspend the redrawing of the element if it is not visible to the user. A script that does this could look like that:
var top = rect.y.baseVal;
var bottom = top + rect.height.baseVal;
var height = window.getComputedStyle(div).getPropertyValue('height');
var elementId;
window.onscroll = function () {
// Check if rectangle is in visible area:
// * if not visible, suspend redrawing
// * if visible, unsuspend redrawing for element
var scrollPosition = div.scrollTop;
if (scrollPosition + height < top || scrollPosition > bottom)
elementId = rect.ownerSVGElement.suspendRedraw(60000);
else
rect.ownerSVGElement.unsuspendRedraw(elementId);
}
The good news: You don't need to think about invisible content. Browsers already have optimizations that skip redrawing of invisible content. Usually browsers track the area of an element that is affected by a repainting. If this repaint area does not intersect with the visible area, the element does not get redrawn.
With the rise of smart phones and tablets, the repaint logic improved more and more. New techniques like tiled backing store help to support faster scrolling. The basic idea however is still in use.
What would happen if we were to implement suspendRedraw? This method could be quite a challenge. Imagine you want to suspend the redrawing of the complete SVG document during scrolling. The user starts scrolling, elements that were invisible now appear on the screen. In theory! Because of the repaint logic they were never drawn. And because of the suspension they won't get drawn on scrolling. The screen stays blank. What happens during DOM mutations? Should the browser repaint? These are just some reasons why we did not try to implement it in WebKit. However, Firefox implemented it but removed the feature again. Authors have to be really careful with using suspendRedraw, otherwise it can even be slower!
3) Resample script-based animations
An interesting idea is to use suspendRedraw for a finely grained control of redrawing to synchronize different animations. The functionality however would be limited to SVG content. A newer technology makes it not only possible to synchronize different animations, but also different types of media such as SVG Animation, HTML Canvas or pure JavaScript animations: requestAnimationFrame.
All major browsers are working on implementing the requestAnimationFrame API. According to When can I use..., Firefox, WebKit browsers, Opera and even Internet Explorer have first releases (or at least nightly builds) with this new API. Paul Irish from the Google Chrome's Developer Relations team and Erik Möller from Opera see a growing consistency across implementations already.
No future for suspendRedraw, unsuspendRedraw and unsuspendRedrawAll
Because of the feedback of implementers, the wrong assumption that authors may have, the advanced level of optimizations of browsers nowadays and new technologies like the requestAnimationFrame API, the SVG WG decided to stub these methods out. Future content should not use these methods anymore. However, they stay in the IDL definition of SVGSVGElement for compatibility reasons, but won't have any effect to the rendering.