January 16, 2014

Give browser a chance

Users want fast loading websites with lots of interesting content, useful functionality and polished graphics. The modern browsers do an excellent job rendering all sorts of objects, running computations (via JavaScript, WebGL, Flash, etc) but they cannot predict what you (website author) want them to do unless you tell them. Give browser a chance to render correctly your content the first time.

Typical example: background images. I often see a website with white background. The text appears, I start reading. Suddenly, the background changes to an image with a subtle pattern. To me (the user) this is a flash of unstyled content. The background image (attached to the BODY tag) was downloading while the browser was doing page layout and rendering. Once the image has been downloaded, it was applied to the page, causing a sudden change.

Caching the image by the browser only alleviates this problem, it does not go away entirely. Plus, the first impression is what counts: cold cache, possible long roundtrip lag, all cause the bad user experience.

Solution to background image problem

As the website author you know the image to be applied. So tell the browser what the background color should be right away. Do it as a CSS style inside the HTML page itself to make sure the browser renders right color immediately.

I use ImageMagick convert program to get the average color of an image. For example, for this image:

old map

the following commands prints average red, green and blue values, each between 0 and 255

$ convert old_map.png -scale 1x1\!
-format '%[fx:int(255*r+.5)],%[fx:int(255*g+.5)],%[fx:int(255*b+.5)]' info:-
// prints 246,232,198

I can take this numbers and use them as background color for my page

// index.html
<head>
<style>
body {
  background-color: rgb(246,232,198);
  background-image: url('old_map.png');
}
</style>
...

Now the browser renders the right color from the get go, replacing it with the actual image when it becomes available. Since the image is very close to the average color, the user only sees subtle detail enhancement.

Image layout problem

A related image problem: the browser has no idea what the resolution of your image is going to be. So it computes the image layout, as if the image had no size! When the image finishes downloading, the browser has to recalculate the layout, causing a sudden violent shift of content. Here is a typical example:

<body>
  <p>Intro text</p>
  <img src="rose-small.png">
  <p>More text follows the image</p>
</body>

First, I see just two lines of text, then, after a small delay, the image appears, shifting the second line down. I often see this problem caused by small thumbnail images, suddenly appearing and causing portions of the content to shift. Bad, bad user experience.

Solution to the image layout problem

Give the browser all information it needs to properly lay out the image, which is extremely simple:

<body>
  <p>Intro text</p>
  <img width="800" height="600" src="rose-small.png">
  <p>More text follows the image</p>
</body>

As the website author, you do know the image dimensions, the browser does not. Give the browser a chance.

Complex images

Giving the browser exact image dimensions causes a problem we dealt with before: the element has body background color before being replaced with the actual image. Again, this causes a flash of unstyled content equivalent. Our previous strategy of using average color usually does not work, since any image interesting enough to show is too complex to be represented by a single pixel or average of pixels.

Instead of averaging, I scale down the image to a very, very small thumbnail image. For example, 800x600 pixel image might be scaled to 20x15 thumbnail using convert

convert -resize 20x rose-small.png rose-thumbnail.png

Then I convert thumbnail image to base64 ASCII text using any available tool (I highly recommend grunt-image-embed). I include the base64 encoding data with the page as a background-image property, setting the background size to 100% of the element, stretching the low resolution previous to the same size as the full resolution image

<!DOCTYPE html>
<html>
<head>
<style>
body {
  background-color: rgb(246,232,198);
  background-image: url('old_map.png');
}
img#rose {
  border: 1px solid black;
  background-color: rgb(109,38,23);
  width: 800px;
  height: 600px;
  background-size: 100%;
  background-image: url(data:image/png;base64,iVBORw0KGgoA...
}
</style>
</head>
<body>
  <p>Intro text</p>
  <img id="rose" width="800" height="600" src="rose-small.png">
  <p>More text follows the image</p>
</body>
</html>

Storage costs:

  • The original 800x600 image is 500Kb png
  • 20x15 thumbnail is 19Kb png
  • base64 thumbnail encoding is 26Kb (1/3 larger than the original)
  • smooth user experience without flashes: priceless

Conclusions

You (the website developer) have all the information. Browser can only make intelligent guesses. By transmitting just slightly more information, you can give the browser a chance to render very good page that will not change much after additional content arrives and is rendered. This will improve the users' experience and their perception of your website's speed and polish.

Demos

I placed the original page that forces browser to guess here. I placed the page with extra info (size, background color, background image) here. Please clear the browser's cache before looking at the demos.

update: I have written a small connect-slow middleware for the popular Nodejs Connect server. It allows selective server-side delays based on request urls. For example you can slow down serving images by a few seconds, while letting everything else be served as quick as possible.

author

Follow Gleb Bahmutov @bahmutov, see his projects at glebbahmutov.com