Brainstorming a technique for loading stylesheets w/o blocking painting across browsers.
Comment on original: goo.gl/fQOZu
Historically, the best practice for loading CSS has been to combine all CSS into a single file and load it in the <head>. This turns out to be a performance issue on mobile. If your combined styles are large, painting is blocked on the download of the entire sheet. On mobile, an external stylesheet loaded in the head can easily add 1 to 2 seconds of additional latency to render time. Even an empty external stylesheet will add 500ms of render latency on 3G in the best case.

The proposed technique to address this is to inline the "critical CSS" needed to style the above the fold content on mobile directly in the head of the HTML, and to load any additional CSS needed to render the below the fold from a cacheable, external stylesheet. The amount of CSS needed to style the above the fold on mobile tends to be small (a few kB) so is a perfect candidate for inlining.

I wrote an article in the PerfPlanet calendar talking about the benefits of inlining the minimal CSS needed for initial render (http://calendar.perfplanet.com/2012/make-your-mobile-pages-render-in-under-one-second/). In addition, +Stoyan Stefanov recently wrote about his work to make the Facebook Recommendations plugin faster (http://www.phpied.com/heres-to-a-faster-recommendations-plugin/). One of the key optimizations was inlining the CSS to avoid the render-blocking network round trips that result from loading external CSS in the head.

It turns out that it's hard to efficiently load external sheets without blocking painting. Using a plain <link> tag in the HTML will definitely block rendering, and it can block rendering even if that <link> tag comes after your above the fold content.

An easy way to avoid blocking painting is to add the stylesheet to the document after the first paint has occurred. For instance, loading the sheet in a requestAnimationFrame callback will make sure that content paints before the sheet starts loading. The big drawback here is that the stylesheet starts loading quite late. It would be better to have the option to start loading that external stylesheet very early in the page load, so it becomes available as soon as possible.

So our technique should:
1. start the load of the stylesheet early, and
2. not block layout/painting while that stylesheet is being loaded

Drawing on the standard async JavaScript snippet that is used to load many async JS libraries, and making use of the fact that smart browsers don't block rendering on stylesheets with non-matching media types, I've been experimenting with the following snippet:

var l = document.createElement('link');
l.rel='stylesheet';
l.href='defer.css';
l.media='defer';
l.addEventListener('load', function() { l.media=''; }, false);
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(l, s);

If you are familiar with async JS snippets, this should look mostly familiar to you. The key differences for CSS are the 2 lines:

l.media='defer';
l.addEventListener('load', function() { l.media=''; }, false);

First, we're setting the media type to something that will definitely not match. We're using 'defer' here to document what we are doing, but we could as easily use 'foo' or, as +Scott Jehl does in his test (https://github.com/scottjehl/css-inapplicable-load) 'nonsense query weeee!'. Second, once the stylesheet has finished loading, we flip the media type back to a matching type (in this case the empty string) so the stylesheet matches and styles are applied. This allows us to avoid blocking layout/painting while that sheet is being loaded.

Scott's work shows that this technique is not likely to work in all browsers. Specifcially, IE (even IE 10) blocks rendering on non-matching stylesheets. This is pretty sad. It seems that Firefox has the same problem. I'd like to come up with a snippet that works across all browsers. Scott's http://scottjehl.com/eCSSential/ looks like it is solving similar problems but I'm hoping to come up with a snippet that's about the size of the standard async JS snippet, and eCSSential is a bit larger.

I'm hoping we can come up with a technique for loading stylesheets w/o blocking painting that works in all popular browsers. Do you know of a way to accomplish this in a cross-browser fashion?

+Paul Irish +Ilya Grigorik 
Shared publiclyView activity