5e speculative background images

February 12, 2010 6:09 pm | 13 Comments

This is the fifth of five quick posts about some browser quirks that have come up in the last few weeks.

Chrome and Safari start downloading background images before all styles are available. If a background image style gets overwritten this may cause wasteful downloads.

Background images are used everywhere: buttons, background wallpaper, rounded corners, etc. You specify a background image in CSS like so:

.bgimage { background-image: url("/images/button1.gif"); }

Downloading resources is an area for optimizing performance, so it’s important to understand what causes CSS background images to get downloaded. See if you can answer the following questions about button1.gif:

  1. Suppose no elements in the page use the class “bgimage”. Is button1.gif downloaded?
  2. Suppose an element in the page has the class “bgimage” but also has “display: none” or “visibility: hidden”. Is button1.gif downloaded?
  3. Suppose later in the page a stylesheet gets downloaded and redefines the “bgimage” class like this:
    .bgimage { background-image: url("/images/button2.gif"); }

    Is button1.gif downloaded?

Ready?

The answer to question #1 is “no”. If no elements in the page use the rule, then the background image is not downloaded. This is true in all browsers that I’ve tested.

The answer to question #2 is “depends on the browser”. This might be surprising. Firefox 3.6 and Opera 10.10 do not download button1.gif, but the background image is downloaded in IE 8, Safari 4, and Chrome 4. I don’t have an explanation for this, but I do have a test page: hidden background images. If you have elements with background images that are hidden initially, you should hold off on creating them until after the visible content in the page is rendered.

The answer to question #3 is “depends on the browser”. I find this to be the most interesting behavior to investigate. According to the cascading behavior of CSS, the latter definition of the “bgimage” class should cause the background-image style to use button2.gif. And in all the major browsers this is exactly what happens. But Safari 4 and Chrome 4 are a little more aggressive about fetching background images. They download button1.gif on the speculation that the background-image property won’t be overwritten, and then later download button2.gif when it is overwritten. Here’s the test page: speculative background images.

When my officemate, Steve Lamm, pointed out this behavior to me, my first reaction was “that’s wasteful!” I love prefetching, but I’m not a big fan of most prefetching implementations because they’re too aggressive – they err too far on the side of downloading resources that never get used. After my initial reaction, I thought about this some more. How frequently would this speculative background image downloading be wasteful? I went on a search and couldn’t find any popular web site that overwrote the background-image style. Not one. I’m not saying pages like this don’t exist, I’m just saying it’s very atypical.

On the other hand, this speculative downloading of background images can really help performance and the user’s perception of page speed. Many web sites have multiple stylesheets. If background images don’t start downloading until all stylesheets are done loading, the page takes longer to render. Safari and Chrome’s behavior of downloading a background image as soon as an element needs it, even if one or more stylesheets are still downloading, is a nice performance optimization.

That’s a nice way to finish the week. Next week: my Browser Performance Wishlist.

The five posts in this series are:

13 Responses to 5e speculative background images

  1. The Firefox 3.6/Opera 10.10 solution to question #2 seems sub-optimal. I would think it is safe to assume that any hidden/invisible element will become visible at some point in time (otherwise, why does it exist?). So at some point in time, likely due to some user interaction, the element will become visible and the background image will become necessary immediately. In Firefox and Opera, that would result in the user seeing the element without its intended background image during the time in which the browser is requesting and downloading the image. There might be other reasons to avoid creating an element until it is visible, but in order to optimize perceived performance, creating the hidden element and pre-fetching the image seems best.

  2. You rock Steve !! What ever you write is very much informative and also they are done with some sample tests instead of boring explanation. I am learning a lot, thanks for that.

    Cheers, Karthik

  3. Definitely a favorite! Intresting post. Thanks

  4. Nice job Steve. This is one of the aspects that I wanted to analyse, thanks a lot for sharing.

  5. I routinely overwrite background images, but it’s only for IE6 and only to get around IE6’s inability to render transparent PNGs.

  6. I’ve already filed a bug about the third behavior : https://bugs.webkit.org/show_bug.cgi?id=31630

  7. For what it’s worth, on item #3 Gecko had the current Webkit behavior for a bit, and we considered it a bug that we did so (https://bugzilla.mozilla.org/show_bug.cgi?id=517224 to be exact).

    On point 2 all that’s going on in Gecko is that we start downloading the image once we know that some element will use it as a background. But for display:none or visibility:hidden elements we never even compute the computed background style (since it’s not needed), so never find out that some element wants the background image. It’s not a purposeful decision to not download those images, just an artifact of when background computation is optimized away altogether.

  8. To add a real-world case where this is relevant: A web application styled with a default stylesheet containing default background images, and a per-client customized stylesheet (“skin”) containing branding graphics etc later in the stylesheet chain.

    I am currently working on exactly that. Thank you, you make a very good point about wasting bandwidth, especially in conjunction with image preloading scripts. I shall store the default styling, at least as far as it is superseded by client-specific styles, into a separate stylesheet.

  9. To offer a reasonable use for item #3, I propose the following:

    a { backgound: url(hover.gif); }
    a:link { background: url(link.gif); }
    a:hover { background: url(hover.gif); }

    With the above rules, the hover image would be pre-fetched for the hover event and prevent the lag in rendering the new background image on hover. The same prefetch behavior could be used another way:

    button { background:url(hover.gif); }
    button { background:url(button.gif); }
    button:hover { background:url(hover.gif); }

  10. I consider #3 a bug. In some situations, say using a complex JS UI framework like ExtJS, there will be a premade CSS design that devs will use and then modify. Now, while I agree that a large site with the right resources should not rely on CSS cascading for this purpose, I don’t think those with lesser resources should be punished in this way.

    I still think a manifest file, similar to that used for offline, should be provided by a website to optionally give the preferred order of downloading assets (both those that can be found from a static analysis of html, css, etc., as well as those that the site dev knows will be needed dynamically at a later point in time).

  11. I’ve recently been playing with CSS gradients. For old browsers, I would declare a background image that simulated the gradient I wanted, then overwrite it with the gradient:

    #el {
    background: red url(gradient.png);
    background: -webkit-gradient(…);
    background: -moz-linear-gradient(…);
    background: linear-gradient(…);
    }

    I noticed Safari was downloading gradient.png, even though it wasn’t using it. To get around this, I had to use Modernizr (with some slight modifications) to target different capabilities:

    .nojs #el, .no-cssgradients #el {
    background: red url(gradient.png);
    }

    .cssgradients #el {
    background: -webkit-gradient(…);
    background: -moz-linear-gradient(…);
    background: linear-gradient(…);
    }

    I don’t like that it has to be done that way, but some of the gradient files were big enough that I ONLY wanted users to get them if they didn’t have another option.

    The spam blocker is asking me the answer to “for times 6”. I’m assuming it meant “four”, or the question makes no sense at all.

  12. Another add to that:
    When Opera parses a background declaration and encounters -moz- or -webkit-gradients it will still interpret the parts it understands (e.g. color), instead of throwing the declaration away completely and using a previous one like it should. That’s why I had to split my background declaration into its parts:
    background-color: #d9d9d9;
    background-position: 0px 0px;
    background-repeat: no-repeat;
    background-image: url(“../img/background.png”);
    background-image:-webkit-gradient(radial, 15% 30, 500, 30% 30, 0, from(#d9d9d9), to(#fff));
    background-image:-moz-radial-gradient(25% 30px, circle cover, #fff 0%, #d9d9d9 500px);
    /* Fail for Webkit: downloads background image even though it doesn’t need it, Firefox 3.6 is more clever
    Also fail for Opera: it doesn’t know -moz- or -webkit-gradients but interprets the part of a
    background-declaration it understands instead of throwing it away and using the previous declaration */

  13. This issue becomes even more relevant in the context of ‘mobile-first’ responsive design. In a streamlined mobile-specific interface, various regions of the page might be initially hidden with a display:none. I probably don’t want mobile users to have to download images for elements that have display:none; they might only click on one out of 10 sections, so they shouldn’t have to download 10 images.

    (Yes, there si the argument that downloading a bgnd image only when the element becomes visible will cause a visual delay and will reduce the ‘snappy’ feel of the interface, but that’s a separate debate).

    Does anyone have any test results about how the *mobile* browsers treat background images when display: none is set?

    (PS. the anti-spam question is asking me for four plus tin. Either the answer is 14, or I’m going to have to refer to an alchemist).