The Cost of Reflows on a Web App

Improving Web Performance for Mobile Web and HTML-Based Apps

Mobile Development, Tutorials Comments (5)

The mobile Web app is gaining a lot of momentum as developers are discovering how powerful Web technologies can be and how their apps can benefit from HTML/CSS/JavaScript. What we love about using Web technologies to build our apps and wrapping them in PhoneGap is that it reuses a lot of knowledge that we’ve already mastered. Years had gone into developing our knowledge of HTML/CSS/JavaScript and instead of tossing all that out the window for mobile, we’re able to refine that knowledge even further. One thing we’re finding, however, is that we can’t build apps the same way we build websites–-obviously from a design standpoint, but also from a development standpoint.

The Reflow

The seemingly simple way a Web page is displayed is actually a pretty complex process: the initial display of a page triggers what is known as a reflow. During the reflow process, the content is combined with rules defined in the stylesheets and the measurements for the layout of the page are determined. After this process is completed, then the actual pixel data is rendered and painted to the screen. Every node on a page impacts the time it takes to do a reflow. On a desktop browser, often the performance impact seems to be relatively unnoticeable. The user impact at a 10ms reflow time is rather negligible. However, herein seems to be much of the performance loss on mobile devices. The same pages that take only 10ms to reflow on a desktop can take over 1000ms on a mobile device. Every single change to the DOM further impacts that time (this is part of the reason why JavaScript-based animation looks so terrible on mobile devices).

Reducing the Cost of a Reflow

So what does this mean for semantics and a mobile app? On the Web, semantic HTML is critical to a website being searchable by search engines like Google, as well as accessible to those using screen readers. HTML5 brings with it a whole host of new tags like <article> and <section> that help make the markup even more semantic and helps avoid the dreaded <div> soup that often plagued designs of any sort of complexity. However we’re finding that semantics is often needing to take a backseat to making efficient use of our HTML. For example, to generate a tableview-like structure, we use an unordered list. Instead of wrapping the content of each list item in an <a> tag, we simply listen for user interaction directly on the <li> node and remove the <a> all together. (Truth be told, we’re finding that a lot of our <a> tags are extraneous.) We’ve also started applying some basic styling to the <html> tag to avoid wrapping the entire body in a <div> when our content needs a wrapper (we figured the <html> tag was already there–might as well use it for something). Finally, we haven’t been using the new tags offered with HTML5 (like <section> or <article>) very often simply because <div> is shorter.

What we’re working toward here is developing our apps so they spend less time in reflow. We’ve found that it is likely the reflow time that causes mobile web apps to “feel slow.” First, we need to shorten the time it takes to perform a reflow. We take care of this by removing extraneous tags: removing <a> tags when we can just listen for tap events on an <img> or <li>, cutting down on <div> wrappers, etc. Next, we need to limit the number of times a reflow is required. A reflow is triggered whenever the DOM is changed or the dimensions of an element change–this could be adding/removing an element or text, or changing the margins or font size of an element.

Reducing the Number of Reflows

The developer tools provided with the desktop version of Safari provide a great way to determine how changes to a page affect the need for a reflow/repaint. While it doesn’t give any sort of indication how long a reflow/repaint would take on a mobile device, using the timeline tab in the developer tools in Safari does give a good indication of how many reflows would be required (since Mobile Safari renders pages similarly to the desktop version). Unfortunately, we have yet to find a suitable tool for watching Mobile Safari’s processes.

Let’s say you’re adjusting the style of a node in your JavaScript:

var node = document.getElementById('some_element');
node.style.border = '2px solid #0F0';
node.style.margin = '5px';
node.style.height = '30px';

This “simple” style adjustment triggered three reflow/repaints of the page. On a desktop a reflow probably takes less than 15ms so it’s not a big deal. However, on a mobile device, a reflow can easily seem to take over 500ms which means that three reflows triggered in rapid succession could easily take a mobile device well over a second to finish processing. To avoid this, use classes to modify an element’s style whenever possible. Style changes applied because of a change in classes only triggers one reflow. Alternatively, use the cssText property to affect all the styles at once (again, only triggering one reflow):

node.style.cssText = 'border:2px solid #0F0;margin:5px;height:30px';

Beyond changing the dimensions of an element, there are times when you may need to make a lot of changes to the DOM (perhaps when drastically updating a view). Unfortunately, every modification made to the DOM requires a reflow/repaint. Below shows the reflow/repaint cost of replacing the header on Float’s mobile site with the word “hi” wrapped in a <div>:

To avoid a significant number of reflows being triggered, we need a way to modify the DOM without triggering reflows. The way to do that is to clone a node in the DOM, make changes to the clone, and then swap the old node with the new node.

var node = document.getElementById('some_element'),
new_node = node.cloneNode(true); // We want this to be a deep clone
// Apply changes to the cloned node
var a = document.createElement('span'),
var b = document.createTextNode('some content');
a.appendChild(b);
new_node.innerHTML = ''; // Empty all the children out of the node
new_node.appendChild(a);
// Out with the old, in with the new
node.parentNode.replaceChild(new_node, node);

This will only trigger one (or maybe two) reflow/repaints where as making the changes directly on the element would have triggered four or five. The same principle applies when using a JavaScript framework like jQuery or Zepto.js–clone the item you’re modifying, make the modifications, and then swap it into the DOM. Here is the cost of the same modifications to the Float mobile site using a clone of the header:

We also tested what the cost would be if we first hid the header (display:none), made the changes, and then made it visible again (display:block). A hidden element doesn’t affect the layout of the page, so it shouldn’t trigger a repaint/reflow, right? Well, sorta. Below is the results of hiding the header, making the changes, and then making it visible again.

Seems like while an element is hidden, Safari still recalculates style (although doesn’t attempt a reflow until we set display back to block). This seems to be an improvement over simply making changes directly to the DOM, but we’re going to opt for the clone, modify, and swap method.

Last month, we looked at using template files to help keep a project organized, but they are also very helpful toward performance optimization as well. Because the markup of a template is determined before being added to the DOM, it only triggers one reflow when adding an entire new view–making it rather inexpensive to manage a layout using templates. We used Safari to determine the cost of switching back and forth between two views from this demo from that post:

Despite the DOM of the page changing dramatically when I select a tweet and then click back, it only causes two reflows to occur.

Conclusion

In October, Apple held its Back to the Mac event where it turned its focus from mobile back to its desktop/laptop software. It also made that point that the developments made for mobile held a lot of relevance for the Mac as well (especially in terms of user experience). We made a similar realization that some of these performance enhancements we should have been utilizing in our websites all along. It’s not so much that we couldn’t reuse our Web development skills, but that they simply needed a lot of improvement.

We see a lot of comments that the mobile web browser needs a lot of work before its ready to handle complicated apps, and while that’s true, Web development skills also need to be improved. The mobile device is simply not as lenient as a desktop device. Let’s not forget, these devices have a fraction of the processing power, memory and bandwidth as a their PC counterparts. Every aspect of the mobile design and development process must be reexamined for efficiences and places where you can improve your skills and know-how in order to provide better end products for your users.

Where are you finding need for improvement in your own Web development skills that you never realized before going mobile?

If you know you need to build a mobile Web app, but you need some consulting help, feel free to contact us.

Follow Float
The following two tabs change content below.

» Mobile Development, Tutorials » The Cost of Reflows on...
On June 7, 2011
By
, ,

5 Responses to The Cost of Reflows on a Web App

  1. AJ Naidas says:

    Hi. I am currently constructing a magazine app for my company. Actually I constructed a magazine previously using PhoneGap and the performance was horrible. It was then that I came to this post and I don’t really know how to implement the javascript templating method in my app. Basically, the pages of my magazine is contained in an overflow:hidden container and is page-switched via css3 translates. How would templating help speed up my app in my situation?

    • Daniel Pfeiffer says:

      Hi AJ,

      I can’t guarantee that using some sort of templating will speed up your app–your performance hit may be coming from somewhere else. What I can tell you is that even if DOM elements are not being displayed on the page, they may still be getting rendered and considered when laying out the flow of the page. (Elements with display:none are not considered during layout while elements with visible:hidden are).

      In my experience, I’ve received the best performance by only having the elements I’m currently using in the DOM–hidden or unused elements I store in a variable or cache somewhere for easy access later. There will be less of a performance hit if the data is just in memory rather than actually part of the DOM.

      Hope that helps!
      –Dan

  2. andyw says:

    Great article, well explained. Still very relevant when using all-in-one frameworks like Sencha Touch.

  3. Soli says:

    HI , I have an performance issue on Ipad caused by reflow event when rotate IPad. After debugging i found that is because of the text is in Arabic language see my question on stackoverflow http://stackoverflow.com/questions/16554205/google-chrome-text-rendering-for-non-latin-languages-is-too-slow
    The question mention chrome but safari on Ipad is 50 times worst than desktop chrome
    Is there any work around for this issue?

    • Daniel Pfeiffer says:

      I’m afraid I don’t have a lot of experiences working with right to left languages. But, I took a look at your StackOverflow post and saw that there definitely seemed to be some performance issues unique to right to left text. I don’t know how feasible this is within your scenario, but you could try setting static heights and widths (based on pixels instead of percentages) for containers to reduce the amount of time it needs to spend calculating the layout and thus the amount of time it spends rendering the layout.

Leave a Reply

Your email address will not be published. Required fields are marked *

« »