No, CSS isn’t always faster

Recently, a post appeared on Hacker News that was yet another exploration of crazy things that can be achieved with CSS.  Like many others, this example was logos rendered using HTML & CSS.  I quickly commented that I didn’t feel we needed more of these examples which, while cool, do almost nothing to advance web development in general. (Note: as Nicolas Gallagher, mentioned in the comments, his article clearly states that these his icons are an experimentation and shouldn’t be deployed in a real-world environment; apologies if the initial text implied that these demonstrations are endorsements of the “use this in the wild” approach).  I suggested that the people involved in such endeavors would be better suited devoting their efforts to demonstrating tips and techniques applicable to the real world.

While I’m sure I could devote an entire post to why CSS is the wrong tool for this job (see the “related reading” section below for a great argument there), one response to my comment in particular caught my attention:

A CSS logo would be useful for performance issues and overall page rendering speed. A CSS animated logo, such as the Atari example, even moreso than a Flash / JS counterpart. CSS renders more quickly than images, and definitely more quickly than embedded Flash.

It’s time for this misinformed, dogmatic approach to web performance to end.  

When Steve Souders came out with his book, High Performance Websites, and the accompanying YSlow tool, the race to reduce HTTP requests was on! Several years later, it’s somewhat unfortunate that in all of the great advice that Steve doled out, many people never make it past this first bullet point. It’s also unfortunate that this rule set has defined website performance for the masses primarily in terms of network-related bottlenecks.  It’s some to start thinking about performance from a holistic point of view; and not isolating things like network performance and elevating them to higher importance than other concerns.

Note: I mention Steve Souders’ work here here not to blame him for what’s happened; more to make the point that good practices (like those Steve suggests) put to use by people without understanding the full ramifications of that advice can lead to some nasty misconceptions.

Thinking about performance holistically, I’m immediately reminded of the talks John Resig did shortly after Sizzle was released and competitors started popping up everywhere to convince people that selector engines were no longer the performance bottleneck in Javascript. People had become obsessed with a single point of performance and were ignoring the “bigger fish.”

So here we are in 2011, and people are still regurgitating the idea that “using CSS is faster than images” without truly understanding the implications of that statement (and how untrue it can be under the right circumstances).  An analogy that I like to use comes from the world of 3D gaming: Rendering something (for example, a drop shadow) in CSS and markup is the real-time, in-game rendering while downloading an appropriately compressed drop shadow png image is using a pre-rendered full-motion-video cut scene. Sure, the initial payload may be somewhat heavier; but again, looking at the complete picture, pre-rendering allows for overall higher performance.

I mention the drop shadow example because it’s absolutely real.  When the team at Netflix was building the initial versions of our web-based UI for connected devices, we ran into serious performance problems on all sorts of devices like the PlayStation3. One of the things we found that significantly sped up the application was to avoid using CSS effects like shadows, gradients, and other “CSS effects;” instead, favoring pre-rendered effects. In some cases, the performance gains were nothing short of astonishing.

To the examples provided by the initial Hacker News article, let’s look at the Twitter logo demonstration. It’s 27 DOM elements and over 4 kilobytes of CSS.  So already we have a comparably sized payload, a more complex DOM, and now it’s time to add flow and paint operations to actually do the rendering of the elements.  There’s a lot of calculations happening to position and style the elements to appear as the Twitter logo.  By using an image, we bypass the calculations on layout and painting that need to be done to do the rendering; those calculations were already completed.

Performance is more than optimizing for the network layer and initial load-time of a page. As with anything, there are tradeoffs to be made (add a little download time, speed up the rendering) and in different situations, there will be different speed winners based on those tradeoffs. More interesting when working with lower-end devices (such as mobile devices) are the tradeoffs between memory use and computation cycles that I hope to cover in depth in future blog posts.

Sadly, there are few resources that cover the topic of in-browser performance in any more depth than a cursory level. I’ve only touched on it here; though I do hope to continue writing about it. That said, before we can even start talking intelligently about working on performance from a holistic approach rather than focusing on individual concerns, we need to learn to get past the dogmatic rules that are holding us back.

Related Reading: Pure CSS Icons: Make the Madness Stop

Performance is in the Details

This past week, some colleagues and I sat around a brainstorming table trying to isolate what was causing a particular page to load incredibly slowly in Internet Explorer 6. Drawing on our combined years of experience, and our mutual disdain for IE6, we came up with what we believed to be a solid list of potential bottlenecks that included (among other things):

  1. DOM Parsing (the DOM is very large for this particular page)
  2. Slow / Inefficient Ajax requests
  3. Parsing large JSON strings returned by Ajax request
  4. Too many event handler assignments

We tested each of these theories among the 5-6 others that we had collected and could not find any particular area that was acting particularly slow. Unfortunately, the lack of any serious profiling tools for IE made the task of doing real benchmarks almost impossible. We scratched our heads and adjourned for our 3-day weekend.

In the process of fixing random software defects, one of our other lead engineers stumbled across this line of code that one of our more junior engineers had checked in:

for(var i=0; i < $$('div.contents').length; i++)

My head was spinning just looking at that one statement. For those unfamiliar with Prototype, the $$ method is a sometimes useful, but horribly inefficient means of searching the DOM. In plain English, this method will walk the entire DOM of your document, and check if it matches the CSS selector contained within, and add a reference to that element to an array if it does. When the traversal is complete, the array is returned. Do you see the problem now? Each time the loop ran (conceivably 50+ times in this case, however many elements matched div.contents) it would have to do an entire DOM traversal. Additionally, it would have to calculate the length of the returned array each time!

For reference, as a service to all of the other engineers-those who don't yet know as well as those who have had to clean up after someone who didn't know, better practice would be:

var contents = $$('div.contents');
var length = contents.length;
for(var i=0; i < length; i++)

However, it would be far more advisable to restrict your search of the DOM using prototype's Element.select method to only check the portions of the DOM.

The important take-away here is that you need to exercise extreme caution when using any method that traverses the DOM; especially as long as IE6 is a large player in the browser market as its DOM traversal is notoriously slow. Prototype's $$ is an especially dangerous method (even when used properly) and should only be employed in cases when it truly makes sense—that is, when you need to check the entirety of the document. Far more often, you will know a containing element from which to base your search:

var oddRows = $("container").select("li.odd");

Finally, always remember to calculate all of your parameters outside of a for statement (as in example 2 above). Doing so as part of the statement frankly reeks of the kind of amateur development practice one might get from reading a collection of some of the worst technical books on the market as for some reason they all seem to advocate this kind of thing.

DOM Traversal is expensive. Know what you're looking for and determine the best way to find it; not the easiest.