Reflows & Repaints: CSS Performance making your JavaScript slow?

I’ve been tweeting and posting to delicious about reflows and repaints, but hadn’t mentioned either in a talk or blog post yet.

I first started thinking about reflows and repaints after a firey exchange with Mr. Glazman at ParisWeb. I may be stubborn, but I did actually listen to his arguments. 🙂 Stoyan and I began discussing ways to quantify the problem.

Going forward the performance community needs to partner more with browser vendors in addition to our more typical black box experiments. Browser makers know what is costly or irrelevant in terms of performance. Opera lists repaint and reflow as one of the three main contributors to sluggish JavaScript, so it definitely seems worth a look.

Let’s start with a little background information. A repaint occurs when changes are made to an elements skin that changes visibility, but do not affect its layout. Examples of this include outline, visibility, or background color. According to Opera, repaint is expensive because the browser must verify the visibility of all other nodes in the DOM tree. A reflow is even more critical to performance because it involves changes that affect the layout of a portion of the page (or the whole page). Reflow of an element causes the subsequent reflow of all child and ancestor elements as well as any elements following it in the DOM.

For example:

<body>
<div class=”error”>
	<h4>My Module</h4>
	<p><strong>Error:</strong>Description of the error…</p>
	<h5>Corrective action required:</h5>
	<ol>
		<li>Step one</li>
		<li>Step two</li>
	</ol>
</div>
</body>

In the html snippet above, a reflow on the paragraph would trigger a reflow of the strong because it is a child node. It would also cause a reflow of the ancestors (div.error and body – depending on the browser). In addition, the h5 and ol would be reflowed simply because they follow that element in the DOM. According to Opera, most reflows essentially cause the page to be re-rendered:

Reflows are very expensive in terms of performance, and is one of the main causes of slow DOM scripts, especially on devices with low processing power, such as phones. In many cases, they are equivalent to laying out the entire page again.

So, if they’re so awful for performance, what causes a reflow?

Unfortunately, lots of things. Among them some which are particularly relevant when writing CSS:

  • Resizing the window
  • Changing the font
  • Adding or removing a stylesheet
  • Content changes, such as a user typing text in
    an input box
  • Activation of CSS pseudo classes such as :hover (in IE the activation of the pseudo class of a sibling)
  • Manipulating the class attribute
  • A script manipulating the DOM
  • Calculating offsetWidth and offsetHeight
  • Setting a property of the style attribute

Mozilla article about reflows that outlines causes and when they could be reduced.

How to avoid reflows or at least minimize their impact on performance?

Note: I’m limiting myself to discussing the CSS impact of reflows, if you are a JavaScripter I’d definitely recommend reading my reflow links, there is some really good stuff there that isn’t directly related to CSS.

  1. Change classes on the element you wish to style (as low in the dom tree as possible)
  2. Avoid setting multiple inline styles
  3. Apply animations to elements that are position fixed or absolute
  4. Trade smoothness for speed
  5. Avoid tables for layout
  6. Avoid JavaScript expressions in the CSS (IE only)

Change classes as low in the dom tree as possible

Reflows can be top-down or bottom-up as reflow information is passed to surrounding nodes. Reflows are unavoidable, but you can reduce their impact. Change classes as low in the dom tree as possible and thus limit the scope of the reflow to as few nodes as possible. For example, you should avoid changing a class on wrapper elements to affect the display of child nodes. Object oriented css always attempts to attach classes to the object (DOM node or nodes) they affect, but in this case it has the added performance benefit of minimizing the impact of reflows.

Avoid setting multiple inline styles

We all know interacting with the DOM is slow. We try to group changes in an invisible DOM tree fragment and then cause only one reflow when the entire change is applied to the DOM. Similarly, setting styles via the style attribute cause reflows. Avoid setting multiple inline styles which would each cause a reflow, the styles should be combined in an external class which would cause only one reflow when the class attribute of the element is manipulated.

Apply animations with position fixed or absolute

Apply animations to elements that are position fixed or absolute. They don’t affect other elements layout, so they will only cause a repaint rather than a full reflow. This is much less costly.

Trade smoothness for speed

Opera also advises that we trade smoothness for speed. What they mean by this is that you may want to move an animation 1 pixel at a time, but if the animation and subsequent reflows use 100% of the CPU the animation will seem jumpy as the browser struggles to update the flow. Moving the animated element by 3 pixels at a time may seem slightly less smooth on very fast machines, but it won’t cause CPU thrashing on slower machines and mobile devices.

Avoid tables for layout (or set table-layout fixed)

Avoid tables for layout. As if you needed another reason to avoid them, tables often require multiple passes before the layout is completely established because they are one of the rare cases where elements can affect the display of other elements that came before them on the DOM. Imagine a cell at the end of the table with very wide content that causes the column to be completely resized. This is why tables are not rendered progressively in all browsers (thanks to Bill Scott for this tip) and yet another reason why they are a bad idea for layout. According to Mozilla, even minor changes will cause reflows of all other nodes in the table.

Jenny Donnelly, the owner of the YUI data table widget, recommends using a fixed layout for data tables to allow a more efficient layout algorithm. Any value for table-layout other than "auto" will trigger a fixed layout and allow the table to render row by row according to the CSS 2.1 specification. Quirksmode shows that browser support for the table-layout property is good across all major browsers.

In this manner, the user agent can begin to lay out the table once the entire first row has been received. Cells in subsequent rows do not affect column widths. Any cell that has content that overflows uses the ‘overflow’ property to determine whether to clip the overflow content.

Fixed layout, CSS 2.1 Specification

This algorithm may be inefficient since it requires the user agent to have access to all the content in the table before determining the final layout and may demand more than one pass.

Automatic layout, CSS 2.1 Specification

Avoid JavaScript expressions in the CSS

This rule is an oldie but goodie. The main reason these expressions are so costly is because they are recalculated each time the document, or part of the document, reflows. As we have seen from all the many things that trigger a reflow, it can occur thousands and thousands of times per second. Beware!

Further study

The Yahoo! Exceptional Performance team ran an experiment to determine the optimal method to include an external stylesheet. We recommended putting a link tag in the head because, while it was one second slower (6.3 to 7.3 seconds) all the other methods blocked progressive rendering. While progressive rendering is non-negotiable (users hate staring at a blank screen), it does make me curious about the effects of rendering, repaints, reflows and resulting CPU thrashing on component download and overall response time. If we could reduce the number of reflows during loading could we maybe gain back a tenth of the lost time (100ms)? What if it was as much as half?

At SXSW I was trying to convince Steve that reflows are important by telling him about an experiment I’ve been meaning to run for a long time, but just haven’t had time. I do hope someone can pick up where I left off (hint! hint!). While loading the page I’d like to intentionally trigger reflows at various rates. This could perhaps be accomplished by toggling a class name on the body (experiment) versus the last child of the body with no descendants (control). By comparing the two, and increasing in the number of reflows per second, we could correlate reflows to response time. Measuring the impact of reflows on JS responsiveness will be harder because anything we do to trigger the reflows will likely impact the experiment.

In the end, quantifying the impact is only mildly interesting, because browser vendors are telling us it matters. Perhaps more interesting is to focus on what causes reflows and how to avoid them. That will require better tools, so I challenge browser vendors and the performance community to work together to make it a reality!

See it in action

Perhaps you are a visual person? These videos are a really cool visualization of the reflow process.

  1. http://www.youtube.com/watch?v=nJtBUHyNBxs
  2. http://www.youtube.com/watch?v=ZTnIxIA5KGw
  3. http://www.youtube.com/watch?v=dndeRnzkJDU

Reflow gone amok

In order to improve performance browser vendors may try to limit reflows from affecting adjacent nodes or combine several reflows into one larger change such as Mozilla’s dirty reflows. This can improve performance, but sometimes it can also cause display problems. You can use what we’ve learned about reflows and trigger them when necessary to correct related display problems.

For example, when toggling between tabs on our image optimization site, http://smush.it, the height of the content is variable from tab to tab. Occasionally the shadow gets left behind as it is several ancestor nodes above the content being toggled and its container may not be reflowed. This image is simulated because the bug is difficult to catch on camera as any attempts to shoot it cause the reflow that corrects it. If you find yourself with a similar bug, move the background images to DOM elements below the content being toggled.

Smush.it! with un-reflowed shadows

Smush it on tab change in Firefox only.

Another example is dynamically adding items to an ordered list. As you increase from 9 to 10 items or 99 to 100 items the numbers in the list will no longer line up properly across all navigators. When the total number increases by an order of magnitude and the browser doesn’t reflow siblings, the alignment is broken. Quickly toggling the display of the entire list or adding a class, even if it has no associated styles, will cause a reflow and correct the alignment.

Tools

A few tools have made waves lately. Stoyan Stefanov and I have been looking for decent ways to measure reflows and repaints and there are a few tools which show promise (despite being very early alpha). Beware, some of these seriously destroyed my browser before I got them working correctly. In most cases you’ll need to have installed the latest nightly builds.

When Mozilla announced the MozAfterPaint Firefox API, the internets were abuzz.

Has anyone else seen any cool tools for evaluating reflows? Please send them my way!

And a couple other tools not directly dealing with reflows.

Ultimately, we need a cross browser tool to quantify and reduce reflows and repaints. I’m hoping that the performance community can partner with browser vendors to make this tool a reality. The browser vendors have been telling us for a while that this was where we needed to look next, the ball is in our court.


Posted

in

, , ,

by

Comments

51 responses to “Reflows & Repaints: CSS Performance making your JavaScript slow?”

  1. […] Stubbornella » Blog Archive » Reflows & Repaints: CSS Performance … […]

  2. dvessel Avatar
    dvessel

    These are excellent point to keep in mind. This is not directly related but one thing I’ve found is the use of CSS tiles or background sprites. If they are massive, it can be more costly when animating. I’ve looked at how the screen redraws the area with Quartz Debug on the Mac and the background images had no effect on what’s redrawn. That makes sense. It goes deeper into how images are handled off-screen.

    I haven’t done any formal tests but I’ve had instances where I created very large (but easily compressed) images for box shadows and rounded corners. The degraded animation was subtle but it was enough for me to break it up into smaller chunks at the cost of more http requests.

  3. dvessel Avatar
    dvessel

    I just realized that enabling “Autoflush drawing” in Quartz Debug illustrates how the page is repainted in step-by-step detail. The relatively small animations on a site I’m working on affected the whole page. This is really eye opening.

    If you didn’t know, Quartz Debug is part of the developers tools included with every Mac. The install is quite hefty but it’s worth it for this utility alone.

    About the visualizations on YouTube.. Could you inform us on how it was created?

  4. […] Sullivan has a very detailed post on reflow and repaints and how they affect performance (and also how to potentially avoid […]

  5. Dave Hyatt Avatar

    Just FYI, very little of what you’ve written here applies to WebKit. There’s nothing overly scary about either a typical reflow or repaint in WebKit as long as what you do doesn’t affect the geometry of the entire page.

  6. Nicole Avatar
    Nicole

    @Dave, Thanks! Just from qualitative experience I had the impression that reflows and animations were smoother with WebKit. Does the same apply to systems with more limited resources like the iphone? Do you have any links related to WebKit / reflows / perf?

    Thanks,
    Nicole

  7. Dave Hyatt Avatar

    The biggest performance problem with reflows is accidentally causing an excessive number of them. This happens when you constantly change something and then query for geometry.

    For example if you do:

    element.style.width = ’50px’;
    var v = element.offsetWidth;
    element.style.width = ’55px’;
    v = element.offsetWidth;

    You just caused two reflows to happen, since asking for offsetWidth forced the element to reflow in order to answer you question (because it had a pending change to style).

    This is the real performance bottleneck to be wary of. Browsers are smart about avoiding reflows when they can, but if you create code that forces a reflow in order to answer a question, then you can create severe performance bottlenecks.

    In some cases, browsers reflow to answer the question even when they don’t need to. This is the case in WebKit with getComputedStyle for example (even if you ask for some style info that has nothing to do with geometry), so that’s something to watch out for in WebKit.

    Basically a reflow/repaint isn’t that bad by itself, but your goal should be to minimize the # of them that occur.

  8. carlosfocker Avatar

    The link for “Kyle Scholz created this tool to visualize paint events before onload.” under “Tools” returns a page not found.

  9. Luke Smith Avatar

    Awesome write up and list of resources. Thanks so much for this!

    If you need more granular control of multiple style value on an element, you can avoid multiple reflows by updating the el.style.cssText property. E.g.
    el.style.cssText += ‘; height: ‘+h+’px; width: ‘+w+’px;’;

    The property accepts the string value you would (but shouldn’t) populate the inline style attribute in markup with. With the exception of Opera 9.x, value updates to cssText are immediately normalized to the current string representation of all properties. To avoid this issue in Opera, you can create an element off DOM, set its style.cssText property with that of the target element, change the off-DOM style properties one at a time, then assign the target element’s cssText with that of the off-DOM element. This technique is used in YUI’s StyleSheet util to lessen the blow of reflows triggered by manipulating stylesheet rules.

    FYI, “Avoid JavaScript expressions in the CSS” is the only one of the h4s that isn’t also wrapped in a strong 🙂

  10. ytzong Avatar

    大师,您写的太好了

  11. Ingo Chao Avatar
    Ingo Chao

    Repaints are combined so there’s one MozAfterPaint event per reflow. This event does not reflect how costly the reflow was.

  12. Matt Wilcox Avatar

    I’ve had this issue on a couple of sites. One was animating the backgorund-position of an image on the HTML element. It was used to have clouds move behind a large PNG header. Just doing that made the site chew up 30-50% of the processor. Which is horrible.

    Thanks for the article, interesting bits of info there.

  13. Steve Souders Avatar

    Hi, Nicole!!

    I’ll be presenting new results on reflow wrt CSS Selectors at Web 2.0 Expo this week (Thurs, April 2, 1:30pm). I’ve been doing a lot of testing in this area the last month or so. I agree with David – I don’t see reflows being that expensive per se. They *do* get expensive, however, when the page contains inefficient CSS selectors. I also find that what triggers a re-application of CSS selectors varies dramatically across browsers. I’ll post my slides on Thursday and appreciate any feedback.

  14. KM Avatar
    KM

    Hi,

    I’m surprised to see nothing about overflow:hidden (and scroll). This property creates a solid separation between the inside and the outside of a block. This solid separation splits the complex logic of float, margin-merge, background-spill in two parts (the mentioned inside and outside). Practically this allow browsers to compute separately the inside and outside layouts (except for the size of the block).

    If a reflow happens on one of the two side, we can entirely skip reflowing the other side if the first reflow left the size of the separating block unchanged. This means that a few well positioned overflows could efficiently segment your layout as long as their size isn’t changed.

    WARNING: This is all theoretical, I have never checked actual browser implementation (and not even checked if some other rule from CSS specs I may have forgotten makes all inapplicable).

  15. Nicole Avatar
    Nicole

    @Steve Super, I can’t wait! 🙂

    @KM Overflow hidden can be a bit dangerous in IE because it overrides the bug that causes containers to expand to wrap floats. It can potentially clip content. It will also trigger other browsers to behave the way IE does and expand containers. It can be used (sparingly), but never in combination with a fixed height.

  16. KM Avatar
    KM

    These are all expected behaviors once you have a “solid separation”. When you can’t go through the wall, the only option is beeing clipped or push it (== container expands). You decide between clip or push when you specify or not the container size.

    If specified in pixels, you have the perfect wall and perfect reflow blocker. If specified using other units then you can only have reflow propagation from outside to inside. If not specified then you can only have reflow propagation from inside to outside.

    The “side effects” are not really dangerous if you know what to expect. (And if you never force the size of a container with text inside, even if the text content is always known.)

  17. Lindsey Simon Avatar

    Hey Nicole,
    I’ve been noodling on a bookmarklet/tool for testing reflow times in a few situations – check out http://reflowr.appspot.com/ and try the bookmarklet out on some pages – more in the works on this front coming…

  18. Nicole Avatar
    Nicole

    Lindsey,

    This is really cool indeed. How accurate do you think the measurements are? I noticed quite a bit of variation between the different passes which isn’t too surprising, but do you think that the JS impacts the measurements? Time to monkey around in your code. 😉

    Thanks for this!
    Nicole

  19. Lindsey Simon Avatar

    @Nicole: I suspect *loads* of things impact these numbers =) Time to find out what/how! What page were you trying?

  20. Nicole Avatar
    Nicole

    Hi Lindsey,

    It seems like each successive test is slower. I saw variance on the order of 2-3X, which may mean the signal gets lost in the noise. I wonder if appending a style node in the head with the css inside would be better than using elem.style. It seems the later has its own perf implications. Then you could do something more interesting with width for example (now it only impacts a wrapper, so probably only causes a repaint rather than reflow).

    I tested bbc and google search results page on Safari and FF.

    Do you tweet btw?

    Nicole

  21. […] Reflows & Repaints: CSS Performance making your JavaScript slow? – Was Reflows und Repaints sind und wie man mit diesen Performancegräbern […]

  22. […] Stubbornella » Blog Archive » Reflows & Repaints: CSS Performance making your JavaScript slow? (tags: javascript css tips performance web_dev) […]

  23. […] reprend donc tout ça avec d’un côté Nicole Sullivan qui fait un billet “reflow & repaint“. Elle y répertorie quelques causes de reflow et quelques recommandations simples pour les […]

  24. […] 详情点这里:《Reflows & Repaints: CSS Performance making your JavaScript slow?》 […]

  25. Dan Olsen Avatar

    Thanks for your great post, Nicole. I saw Steve Souders speak on this topic at the Web 2.0 Expo. It’s one of the new performance topics in his upcoming sequel “Even Faster Websites”.

    His slides are posted at: http://stevesouders.com/docs/web20expo-20090402.ppt
    In Slide 35 he presents his thorough analysis of reflow times segmented by browser on one axis and by JavaScript event type on the other axis, so you can see which events are most costly in which browsers.

    I was also impressed by Steve’s analysis of CSS selector performance (Slides 26-31). He took inspiration from another study but then fine-tuned the experiment to ensure the results were prototypical of real-world webpages.

    Thanks again. Will try to catch your talk at the Percona Performance Conference http://conferences.percona.com/percona-performance-conference-2009/schedule.html

  26. […] Change classes on the element you wish to style (as low in the dom tree as possible) […]

  27. […] 性能工程师 Nicole Sullivan 在最新的文章 《Reflows & Repaints: CSS Performance making your JavaScript slow?》 中总结了导致 reflow […]

  28. […] Stubbornella » Blog Archive » Reflows & Repaints: CSS Performance making your JavaScript slow? […]

  29. […] Sullivan写了一篇非常值得一读的分析Reflowå’ŒRepaint的文章。 […]

  30. […] going all through it and reading everyone’s lists of tips on increasing performance, there still weren’t a lot of apparent problem areas. The main […]

  31. […] so much as reads the offsetHeight of an element, it’s likely that the browser will need to reflow the page to calculate the value. Repeat that for enough lines, and you’ve got a bit of a performance […]

  32. […] 原文:Reflows & Repaints: CSS Performance making your JavaScript slow? 作者:Nicole Sullivan […]

  33. […] Yahoo!性能工程师Nicole Sullivan写了一篇非常值得一读的分析Reflowå’ŒRepaint的文章。 […]

  34. […] Nicole Sullivan 的关于 reflow å’Œ 重绘(Nicole Sullivan on Reflows and Repaints) […]

  35. […] Nicole Sullivan 的关于 reflow å’Œ 重绘(Nicole Sullivan on Reflows and Repaints) […]

  36. […] Nicole Sullivan 的关于 reflow å’Œ 重绘(Nicole Sullivan on Reflows and Repaints) 这篇文章发布于 2009å¹´12月12日,星期六,9:15 上午,归类于 网页技术。 您可以跟踪这篇文章的评论通过 RSS 2.0 feed。 您可以留下评论,或者从您的站点trackback。 […]

  37. […] paying attention to the way you access the DOM, your application will be slow. This is certainly nowhere near an original statement, but it deserves to be repeated yet again. Firebug, SpeedTracer, and […]

  38. Jacob Fogg Avatar

    Great article! I can concur with what one of the commenter’s said about large sprites and animations. I had my rounded corners and shadows in a sprite with many other elements on my site. the animated panels were very choppy. I dropped just the corners and shadow into a sprite on it’s own, and the results were visible. Now, I am just using HTML 5… I know < IE8 doesn't support them, but I figure it's a sacrifice that's worth while.

  39. Jon Raasch Avatar

    Great thanks for this article. I got linked to it on Stack Overflow, and it really does a great job of demystifying the differences between repaint + reflow.

  40. Webstandard-Blog Avatar

    Great stuff! Thx for sharing those css & javascript performance informations!

  41. […] problem with this is that like everything else in CSS, these styles only get applied when a reflow is triggered. Counterintuitively, rotating the device does NOT trigger a reflow (tested on iOS 4), so a fix for […]

  42. […] As it turns out, orientationchange is only fired AFTER the screen has been rotated (which also triggers a CSS reflow), which means this attributed is updated later (after the reflow). And unfortunately editing this […]

  43. miriam Avatar

    Already bookmarked this is detailed post for any web-designers need. simply thanks and youtube vid link is excellent. 🙂

  44. […] 2. Repaint, reflow/relayout, restyle 3. Reflows & Repaints: CSS Performance making your JavaScript slow? 4. Repaint 跟踪浏览器的渲染[如果你的FF是3.5beta+监听网页的重绘情况] css, […]

  45. […] 2. Repaint, reflow/relayout, restyle 3. Reflows & Repaints: CSS Performance making your JavaScript slow? 4. Repaint 跟踪浏览器的渲染[如果你的FF是3.5beta+监听网页的重绘情况] […]

  46. […] Voici l’explication trouvée par Tommy dans sa version originale : “ You just caused two reflows to happen, since asking for offsetWidth forced the element to reflow in … […]