Archive for the 'images' Category

Canvas pixels #3: getUserMedia

Wednesday, June 13th, 2012

getUserMedia() is a proposal for one of the most desired device APIs that can give HTML and JS access to the user's camera and microphone. It's already available in Chrome Canary in the form of navigator.webkitGetUserMedia(). It's also available in Opera without a prefix.

In part #1 of this miniseries I talked about manipulating pixels in canvas, let's do the same but this time using video data from your own webcam instead of a static image.

Demo

I've only tested in Chrome so if you want to see the demo, you need to:
1. install Canary
2. go to chrome://flags and Enable PeerConnection

If you don't want to go through the trouble, here's a snapshot of what you're missing: a little video element showing your video camera stream and 4 canvas elements where the image data is manipulated in some way.

And the demo

Hooking up the cam

Getting a video stream is pretty straightforward:

navigator.webkitGetUserMedia(
  {video: true},
  iCanHazStream,
  miserableFailure
);

You declare what type of media you want (video in this case) and provide success and failure callbacks. The browser then prompts the user to allow access:

If the user allows, your success callback is called.

Here's mine:

function iCanHazStream(stream) {
  var url = webkitURL.createObjectURL(stream);
  $('video').src = url;
  webkitRequestAnimationFrame(paintOnCanvas);
}

I have a <video id="video"> element on the page and I set its src to be the stream URL. (In Opera you assign the stream directly, not some made up URL. In webkit the URL ends up being something like http://www.phpied.com/files/canvas/blob:http%3A//www.phpied.com/c0d155b9-f4f8-4c4f-b2bc-694de68d74f2. Anyway, not terribly important)

So this is all you need to do in order to display the camera stream in a VIDEO element. Easy right?

Then I have another function paintOnCanvas() which I schedule with the new requestAnimationFrame hotness instead of old school setInterval()

Setting up the canvas

For the image manipulation I'm using the same CanvasImage() constructor from part #1.

During page load I initialize 4 canvas elements with a placeholder image.

var transformadores = [
  new CanvasImage($('canvas1'), 'color-bars.png'),
  new CanvasImage($('canvas2'), 'color-bars.png'),
  new CanvasImage($('canvas3'), 'color-bars.png'),
  new CanvasImage($('canvas4'), 'color-bars.png')
];

And I have 4 simple pixel manipulators like you saw already:

var manipuladors = [
  {
    name: 'negative',
    cb: function(r, g, b) {
      return [255 - r, 255 - g, 255 - b, 255];
    }
  },
  {
    name: 'max blue',
    cb: function(r, g, b) {
      return [r, g, 255, 255];
    }
  },
  {
    name: 'max red',
    cb: function(r, g, b) {
      return [255, g, b, 255];
    }
  },
  {
    name: 'noise',
    cb: function(r, g, b) {
      var rand =  (0.5 - Math.random()) * 50;
      return [r + rand, g + rand, b + rand, 255];
    },
    factor: '(0 - 500+)'
  }
];

Painting on the canvas

Finally, the paintOnCanvas() function. Here's what happens:

function paintOnCanvas() {
  var transformador = transformadores[0];
  transformador.context.drawImage(
    $('video'), 0, 0, 
    transformador.image.width, transformador.image.height
  );
  var data = transformador.getData();
  for (var i = 0; i < 4; i++) {
    transformador = transformadores[i];
    transformador.original = data;
    transformador.transform(manipuladors[i].cb);
  }
  webkitRequestAnimationFrame(paintOnCanvas);
}

First we need to take the image data from the video element and draw it on a canvas. Then read the image data from the canvas, play with it and paint it back. This seems like a hassle, there might be an easier way to get image data from the video or the stream without going stream-video-canvas, but I don't know it. In any event I only do it once for the first canvas, then remember this data and use it for all the 4 canvases.

It's surprisingly easy to draw a video data in canvas, just using context.drawImage(video_dom_element, ...). From there I read the image data into data and loop through the 4 canvas instances, transforming the image using one of the manipulators I have set up.

Once again, for your entertainment, the demo is right here.

 

Canvas pixels #2: convolution matrix

Monday, June 11th, 2012

In the previous post I talked about manipulating and changing pixels in an image (using JavaScript and canvas) one at a time. We took a single pixel and messed around with its R, G, B or A values.

This time let's look into taking account not only the single pixel but the pixels around it. This allows you to do all kinds of effects, the most popular being emboss, edge detection, blur and sharpen.

The demo page is here

Theory

The type of manipulation we'll consider is called image convolution using a 3x3 matrix. You take 9 pixels from the image: the current pixel you're changing and the 8 immediately around it.

In other words you want to change the RGB values for the pixel in the middle based on its own value and those around it.

Let's say we have some sample values (given in red for R, blue for B and green for G in this figure):

Remember this manipulation was called convolution matrix. So you need a matrix. Below is an example of one such matrix (used in the blur effect)

1,2,1,2,4,2,1,2,

Now you take one of the channels, say R for example. You take each of the 9 R values you have and multiply it by the corresponding number in the matrix. Then sum the nine numbers.

1,2,1,2,4,2,1,2,

1*1 + 2*2 + 5*1 + 11*2 + 10*4 + 20*2 + 1*1 + 10*2 + 1*1 =
 1  +  4  + 5   +   22 +  40  +  40  +  1  +  20  +  1  =
                      134 

In addition to the matrix we also have a divisor and an offset, both optional. If there's no divisor (meaning it's 1, not 0), the result for Red we're looking for is 134. As you can see 134 is pretty far off from the original value of 10. But the blur effect has a divisor of 16. So the new value for red is 8.375

If the convolution asked for an offset, you add it to the end result.

Then you repeat the same for Green and Blue. You can do alpha if you want but for regular images it has constant 255 value so you'll do a lot of math and end up with 255.

You may have noticed that the divisor 16 is also the sum of the numbers in the matrix;

1 + 2 + 1 + 2 + 4 + 2 + 1 + 2 + 1 = 16

This way the result image is as bright as the original. If you have an unbalanced matrix you'll get a darker or a lighter image.

The offset is 0 most of the time, but not always. The emboss effect has offset 127 for example.

Demo matrices

My demo uses the most popular matrices out there. You can search the web for other matrices and play with them. None of them define a divisor because it's the sum of their elements, but the API I'll show you lets you use your custom divisor.

Without further ado, here are the matrices I used defined as an array of JavaScript objects:

var matrices = [
  {
    name: 'mean removal (sharpen)',
    data:
     [[-1, -1, -1],
      [-1,  9, -1],
      [-1, -1, -1]]
  },
  {
    name: 'sharpen',
    data:
     [[ 0, -2,  0],
      [-2, 11, -2],
      [ 0, -2,  0]]
  },
  {
    name: 'blur',
    data:
     [[ 1,  2,  1],
      [ 2,  4,  2],
      [ 1,  2,  1]]
  },
  {
    name: 'emboss',
    data:
     [[ 2,  0,  0],
      [ 0, -1,  0],
      [ 0,  0, -1]],
    offset: 127,
  },
  {
    name: 'emboss subtle',
    data:
     [[ 1,  1, -1],
      [ 1,  3, -1],
      [ 1, -1, -1]],
  },
  {
    name: 'edge detect',
    data:
     [[ 1,  1,  1],
      [ 1, -7,  1],
      [ 1,  1,  1]],
  },
  {
    name: 'edge detect 2',
    data:
     [[-5,  0,  0],
      [ 0,  0,  0],
      [ 0,  0,  5]],
  }
];

Results

Original

Blur

Sharpen

Edge detect

Edge 2

Emboss

Emboss (subtle)

Mean removal (sharpen a lot)

The API

The API is the same as in the previous post, same constructor and all, just adding a new method called convolve(). This is where the magic happens.

You use this method like so:

transformador.convolve([
  [1,2,1],
  [2,4,2],
  [1,2,1]
], 16, 0);

Again, 16 is optional as the method will figure it out if you omit and offset is optional too. Actually you can go to the demo and play in the console to see what happens with a different divisor, e.g.

transformador.convolve([[1,2,1],[2,4,2],[1,2,1]], 10);

or

transformador.convolve([[1,2,1],[2,4,2],[1,2,1]], 20);

convolve()

Some comments on how convolve() was implemented in this demo.

The big picture:

CanvasImage.prototype.convolve = function(matrix, divisor, offset) {
  // ...
};

Handle arguments: flat matrix is easier to work with and figure out the divisor if missing. How 'bout that array reduce, eh? ES5 ftw.

  var m = [].concat(matrix[0], matrix[1], matrix[2]); // flatten
  if (!divisor) {
    divisor = m.reduce(function(a, b) {return a + b;}) || 1; // sum
  }

Some vars more or less the same as the last time in the transform() method:

  var olddata = this.original;
  var oldpx = olddata.data;
  var newdata = this.context.createImageData(olddata);
  var newpx = newdata.data
  var len = newpx.length;
  var res = 0;
  var w = this.image.width;

Then a loop through all the image data, filter out every 4th element (because we ignore Alpha channel) and write the new image data to the canvas.

  for (var i = 0; i < len; i++) {
    if ((i + 1) % 4 === 0) {
      newpx[i] = oldpx[i];
      continue;
    }
 
    // 
    // magic...
    //
  }
  this.setData(newdata);

Remember that canvas image data is one long array where 0 is R for pixel #1, 1 is B, 2 is G, 3 is Alpha, 4 is R for pixel #2 and so on. This is different than more other code examples you'll in different languages where there are two loops in order to touch every pixel: one from 0 to width and an inner one from 0 to height.

And finally, the "magic" part:

    res = 0;
    var these = [
      oldpx[i - w * 4 - 4] || oldpx[i],
      oldpx[i - w * 4]     || oldpx[i],
      oldpx[i - w * 4 + 4] || oldpx[i],
      oldpx[i - 4]         || oldpx[i],
      oldpx[i],
      oldpx[i + 4]         || oldpx[i],
      oldpx[i + w * 4 - 4] || oldpx[i],
      oldpx[i + w * 4]     || oldpx[i],
      oldpx[i + w * 4 + 4] || oldpx[i]
    ];
    for (var j = 0; j < 9; j++) {
      res += these[j] * m[j];
    }
    res /= divisor;
    if (offset) {
      res += offset;
    }
    newpx[i] = res;

these are the pixels we want to inspect. oldpx[i] is the one in the middle which we're changing to newpx[i]. Also note how we default all pixels to oldpx[i]. This is to deal with the boundary pixels: tho top and bottom rows of pixels and the left and right columns. Because the pixel in position 0x0 has no pixels above it or to the left. Then we loop through these and multiply by the corresponding value in the matrix. Finally divide and offset, if required.

Thanks!

Thanks for reading, and now go play with the demo in the console. An easy template to start is:

transformador.convolve([[1,0,0],[0,0,0],[0,0,-1]], 1, 127); 

If you want to apply convolutions on top of each other, you can reset the original image data to the current.

transformador.original = transformador.getData();
 

Pixel manipulation in canvas

Sunday, June 10th, 2012

Done it before with PHP, but now that JavaScript is all-powerful, let's see how we can manipulate images in an HTML <canvas>.

The demo is here.

Pixel manipulation

The simplest way to fiddle with image data is to take each pixel and change the value of one or more of its channels: red, green, blue and alpha (transparency), also known as R, G, B and A for short. The results may vary from mostly useless to "wow, that sepia was easy, I'm totally doing it for all my thumbnails!"

A trivial example is to just swap some values. E.g. take the amount of B and assign it to G.

rgb(100, 50, 30, 255) becomes
rgb(100, 30, 50, 255)

Callbacks

For the purpose of this demo I've isolated the part that deals with the canvas API (loading an image file, reading and writing pixels) from the actual pixel manipulation. The manipulation itself is in the form of simple callback functions.

The example above (B to G and G to B) is written as

function (r, g, b) {
  return [r, b, g, 255];
}

Here we ignore alpha, because we don't need it and hardcode it to 255. Here's the result:

from the original:

Let's say you want to touch alpha and make an image partially transparent. Here's the callback:

function (r, g, b, a, factor) {
  return [r, g, b, factor];
}

Here we let the user specify a factor in other words how transparent should the image be. We use this as a value for the alpha channel.

The result of calling this with factor of 111:

And the most complicated form of callback is taking into account where this pixel is in the overall image. Here's a gradient:

function (r, g, b, a, factor, i) {
  var total = this.original.data.length;  
  return [r, g, b, factor + 255 * (total - i) / total];
}

And the result of calling this with factor of 111:

this refers to an object of our own design which you'll see in a bit. It keeps some info in case the callbacks need as is the case above.

The canvas part

For the canvas stuff I started by copying an old post to load an image into a canvas. This constructor was born:

function CanvasImage(canvas, src) {
  // load image in canvas
  var context = canvas.getContext('2d');
  var i = new Image();
  var that = this;
  i.onload = function(){
    canvas.width = i.width;
    canvas.height = i.height;
    context.drawImage(i, 0, 0, i.width, i.height);
 
    // remember the original pixels
    that.original = that.getData();
  };
  i.src = src;
  
  // cache these
  this.context = context;
  this.image = i;
}

You use this by passing a reference to a canvas element somewhere on the page and a URL to an image. The image has to be on the same domain for the image data part to work.

var transformador = new CanvasImage(
  $('canvas'),
  '/wp-content/uploads/2008/05/zlati-nathalie.jpg'
);

The constructor creates a new Image object and once it's loaded it draws it into the canvas. Then it remembers some stuff for later such as the canvas context, the image object and the original image data. `this` is the same `this` that pixel manipulator callbacks have access to.

Then you have three simple methods to get, set and reset the image data into the canvas:

CanvasImage.prototype.getData = function() {
  return this.context.getImageData(0, 0, this.image.width, this.image.height);
};
 
CanvasImage.prototype.setData = function(data) {
  return this.context.putImageData(data, 0, 0);
};
 
CanvasImage.prototype.reset = function() {
  this.setData(this.original);
}

The "brains" of the whole thing is the transform() method. It takes the pixel manipulator callback and a factor which is essentially a configuration setting for the manipulator. Then it loops through all pixels, passes olddata rgba channel values to the callback and uses the return values from the callback as newdata. At the end the new data is written back to canvas.

CanvasImage.prototype.transform = function(fn, factor) {
  var olddata = this.original;
  var oldpx = olddata.data;
  var newdata = this.context.createImageData(olddata);
  var newpx = newdata.data
  var res = [];
  var len = newpx.length;
  for (var i = 0; i < len; i += 4) {
   res = fn.call(this, oldpx[i], oldpx[i+1], oldpx[i+2], oldpx[i+3], factor, i);
   newpx[i]   = res[0]; // r
   newpx[i+1] = res[1]; // g
   newpx[i+2] = res[2]; // b
   newpx[i+3] = res[3]; // a
  }
  this.setData(newdata);
};

Pretty simple, right? The only confusing part could be the i += 4 increment in the loop. See, the data returned by canvas' getImageData().data is an array with 4 elements for each pixel.

Imagine an image with only two pixels - red and blue and no transparency. The data for this image is

[
  255, 0, 0, 255,
  0, 0, 255, 255
]

And this is it. You use the objects created by ImageCanvas constructor like so:

transformador.transform(function(r, g, b, a, factor, i) {
  // image magic here
  return [r, g, b, a];
}, factor);

Feel free to play in the console with this on the demo page

Callbacks (contd.)

The rest of the code for the demo is just the setting up pixel-fiddling callbacks and creating some UI to use them. Let's check out a few.

Greyscale

Grey is equal amounts of r, g, b. So you can simply average the three values to get a single value:

var agv = (r + g + b) / 3;

This is good enough, but there's a secret formula that does a better job for humans, because it takes into account our species' sensitivity to the different channels. The result:

function(r, g, b) {
  var avg = 0.3  * r + 0.59 * g + 0.11 * b;
  return [avg, avg, avg, 255];
}

Sepia

A lazy sepia is to make a greyscale version and then add some color to it - equal amounts of rgb to each pixel. I add 100 to red and 50 to green and I like it, but you can play with different values.

function(r, g, b) {
  var avg = 0.3  * r + 0.59 * g + 0.11 * b;
  return [avg + 100, avg + 50, avg, 255];
}

There's a second sepia callback which is supposed to be better, but I don't like it as much.

Negative (invert)

Subtracting each channel from 255 gives you a negated image:

function(r, g, b) {
  return [255 - r, 255 - g, 255 - b, 255];
}

Noise

Noise is kind of fun, you take a random value between -factor and factor and add it to each channel:

function(r, g, b, a, factor) {
  var rand =  (0.5 - Math.random()) * factor;
  return [r + rand, g + rand, b + rand, 255];
}

Noise with factor of 55:

Your turn

I'm leaving some of these manipulator callbacks for your source exploration and then to your imagination. What can you think of?

Go nuts with those callbacks in the console! Your basic "template" again is:

transformador.transform(function(r, g, b, a, factor, i) {
  // image magic here...
  return [r, g, b, a];
});

Try e.g. - blue to 255. Or make it black and white (not greyscale, make each pixel either 0,0,0 or 255,255,255)

 

Ajax with images

Saturday, June 2nd, 2012

So you can do Ajaxy stuff with XMLHttpRequest or or iframes or dynamic JavaScript tags or... how about simple images. This is best used for simple stuff where you can have a limited number of predefined responses, such as "success" and "oops".

All you do is create an image and set its source and this makes a request:

new Image().src = "mystuff.php";

This is if you don't care about the response. If you want to inspect the response though you can attach onload and onerror handlers:

var i = new Image();
i.onload = function() {
  // inspect the response
};
i.src = "mystuff.php";

If you can assume you'll have "OK" response most of the time, you can have the .php return a 204 No Response which is the smallest response (no body). If the .php determines there's something wrong, it then can return an image.

When you send a 204 response, the onerror handler will be called because the response is not really an image. It looks backwards to have your success handler be called onerror, but if you expect more successes than errors, then it's probably worth it.

var i = new Image();
i.onload = function() {
  // an error occurred
};
i.onerror = function() {
  // success!
};
i.src = "mystuff.php";

And the final thing - if you want to have coded responses, in other words be able to differentiate between different errors (each with its error code), you can have the .php return different image sizes. Say with constant height but varying width. E.g. 1x1 image, 2x1, 3x1 and so on. In the onload you inspect the size of the image and determine the type of response.

var i = new Image();
i.onload = function() {
  // an error occurred
  if (i.width === 1) {
    // error #1
  } 
  if (i.width === 7) {
    // error #7
  } 
// etc...
 
};
i.onerror = function() {
  // success!
};
i.src = "mystuff.php";

I'm using a different errors as an example, but you can always have it the other way around: you consider onload a suceess and there are different types of successes.

Email address validation example

Let's take a look at a little more practical example. Let's validate email addresses on the server.

We'll return 7 different image sizes if the supplied email address is invalid or a 204 response is the email looks fine.

The OK response:

function ok() {
  header("HTTP/1.0 204 No Content");
}

The error response that generates an image with a desired width and height of 1 px:

function image($width) {
  header("Content-Type: image/png");
  $im = imagecreate($width, 1);
  imagecolorallocate($im, 0, 0, 0);
  imagepng($im);
  imagedestroy($im);
  die();
}

The error codes:

// 1x1 = empty input
// 2x1 = missing @
// 3x1 = too many @s
// 4x1 = missing username
// 5x1 = missing host
// 6x1 = blocked
// 7x1 = no dns record for host
// 204 = OK

And, finally, the "business" logic:

$email = (string)@$_GET['email'];
 
if (!$email) {
  image(1);
}
 
// split to username and domain
$splits = explode('@', $email);
 
if (count($splits) === 1) {
  image(2);
}
 
if (count($splits) !== 2) {
  image(3);
}
 
list($user, $host) = $splits;
 
if (!$user) {
  image(4);
}
 
if (!$host) {
  image(5);
}
 
$blocked = array('yahoo.com', 'gmail.com', 'hotmail.com');
 
if (in_array($host, $blocked)) {
  image(6);
}
 
 
if (!checkdnsrr($host)) {
  image(7);
}
 
// all fine, 204
ok();

Test page

You can play with the test page here:
http://www.phpied.com/files/imaje/test.html

The markup:

<input id="i"><button id="b">check email</button><pre id="r">enter an email</pre>

And the JS that makes a requests and checks for ok/error:

 
var i;
$('b').onclick = function() {
  i = new Image();
  i.onerror = function() {
    $('r').innerHTML = "thanks!";
  };
  i.onload = function() {
    $('r').innerHTML = "invalid email address!";
  };
  i.src = "imaje.php?email=" + encodeURIComponent($('i').value);
  $('r').innerHTML = "checking...";
};

All there is to it!

Verbose page

A more verbose test can inspect the width of the returned image and display an appropriate message to the user.

Play with it here:
http://www.phpied.com/files/imaje/verbose.html

var i;
$('b').onclick = function() {
  i = new Image();
  i.onerror = ok;
  i.onload = function() {
    err(i.width);
  }
  i.src = "imaje.php?email=" + encodeURIComponent($('i').value);
  $('r').innerHTML = "checking...";
};
 
function ok() {
  $('r').innerHTML = "this is one valid email address, good for you!";
}
 
function err(num) {
  var errs = [
    '',
    'Seriously, I need an email',
    "Where's the @?",
    "Too many @s",
    "Missing username",
    "Missing mail host",
    "BLOCKED! Go away!",
    "Not a valid mail server",
 
  ];
  $('r').innerHTML = errs[num];
}

A good side effect of using a global i is that async responses don't mess up the result. E.g. you send requests #1 and #2, #2 arrives first and is OK, #1 arrives later and is an error. At this point you've overwritten i and the handlers for #1 are no longer available. Dunno is it's possible but it would be cool to be able to further abort a previous request if you've made one after it.

Thanks

Thanks for reading! I know it's hardly new for you, my two faithful readers, but these responses with varying image size came up in a conversation recently and as it turns out there are rumors that there might be about 3 developers in Chibougamau, Quebec, that are not aware of this technique.

 

Imagemagick crop and center

Wednesday, October 12th, 2011

Say you get a sprite created no matter how but the original images are long gone or for whatever reason not available. With imagemagick it's easy to crop them back. And I think it's easier and more precise than with a graphic UI, e.g. photoshop. Let's see how.

Task: get me a 16x16 favicon for yahoo when all you have is the Yahoo search page

  1. goto yahoo search, right click the icon, Inspect
  2. Download the sprite:
    $ curl http://a.l.yimg.com/pv/i/us/sch/gr5/metro_sprite_20110907.png > ys.png
    

    here it is:

  3. While inspecting the CSS you noticed
    width: 16px;
    height: 12px;
    background-position: -117px -95px;
    

    This helps us cut precisely as opposed to guess the coordinates

  4. Crop it:
    $ convert ys.png -crop 16x12+117+95 yfavicon.png
    

    The result is a 16x12 image:

  5. And because we have this arbitrary requirement that we want all favicons to be 16x16 because our design says so, let's resize it to fit a 16x16 box and center it inside the box
    $ convert yfavicon.png -background transparent -gravity center -extent 16x16 y.png
    

    Here:

  6. And just because, let's make it 160x160 with colored background to make sure the command works
    $ convert yfavicon.png -background orange -gravity center -extent 160x160 yorange.png
    

  7. Enjoy the rest of your day having saved some time by doing things on the command line instead of dealing with UI that gets in the way :)
 

Overlooked Optimizations: Images

Tuesday, June 14th, 2011

#1 This guest post from Billy Hoffman is the last post in the Velocity countdown series. Velocity starts first thing tomorrow! Hope you enjoyed the ride and please welcome Billy Hoffman!

Billy HoffmanBilly Hoffman (@zoompf) is the founder and CEO of Zoompf, a web performance startup whose scanning technology helps website owners find and fix performance issues which are slowing down their sites. Previously Billy was a web security researcher at SPI Dynamics and managed a research team at HP. He can open a Coke can without using his hands.

(tl;dr: Images make up the majority of the Internet, yet we consistently fail to apply the most basic of optimizations. Even big sites like Twitter are completely screwing this up. Furthermore, there are huge unexplored areas when it comes to image optimization which would provide significant savings. We should stop worrying about esoteric performance optimizations when there is so much other low hanging fruit.)

Images constitute the bulk of content on the Internet, both in terms of content size and number of resources. Using data from the wonderful HTTP Archive we see that 60% of the bytes that make up an Alexa Top 1000 website are images. The average webpage references 81 external resources, and 64% of these are images. And the dominance of images is growing. In the last 6 months, total page content size increased by 70 kB. 75% of that increase (52 kB) came from images.

We know that lossless image optimization tools reduce content size anywhere from 5-20%. Occasionally you will see 70% savings or more, but that only happens when the image contains an embedded thumbnail. That level of savings doesn't sound all that impressive. After all HTTP compression can save 60-70% on real world text resources like HTML, JavaScript, or CSS. However text resources only make up on average 188 kB, or 24% of total content size. Saving 66% on 24% of content saves about as much as 5-20% savings on 60% of the content. In fact, if you could reduce images by 25%, that would have more of an effect on reducing total content size than using HTTP compression!

If you work in front-end performance, none of this should be a surprise. Obviously any front-end performance strategy needs to include image optimizations. Image optimization is an old topic. Shouldn't we instead be focusing on more esoteric optimizations, like refactoring CSS rules so that external fonts render faster on Blackberry Webkit? No, we shouldn't, because sadly we collectively suck at optimizing images.

Give PNG a Chance? Nope.

One of the most basic image optimizations that you can make is converting GIFs to PNGs. PNGs can do everything that GIFs can do and more, and the browser issues with PNGs are larger a problem of the past. Even without applying additional lossless optimization tools on the PNG, converting a GIF file will almost without exception create a smaller PNG. This is because the fundamental way graphics data is compressed in a file PNG, using the DEFLATE algorithm, is more efficient than GIF's LZW compression scheme. Once you apply lossless tools on the converted PNG they get even smaller. Animated GIFs are the exception here, as PNGs are not animated and alternatives for simple animations (MNG, Flash) are either not widely supported or result in larger files. So what is the break down of image formats on the web today?

37% of images on the Alexa Top 1000 websites are GIFs. That makes no sense given what we know about PNGs over GIFs. 37% of the images on the Internet are not animated "Under construction" icons or Ajax status thumper animations. People are not being intelligent about file formats they use for images.

The Internet, now with more bloat!

Applying lossless image optimization tools is one of the simplest optimizations to do. Take an image, run a program, get an optimized image. Stoyan and I love optimizations like this because they are so easy to automate. Just add a step to the website build process or to your staging-to-production publishing process that automatically optimizes images. It should be transparent, something you setup once and forget about. So how are we doing?
82% of Alexa Top 1000 websites contain images which were not losslessly optimized. Apply lossless optimizations across all the images from the Alexa top 1000 would reduce file size by an additional 15%.

Surely there are just a few number of smaller sites which aren't properly optimizing images which are pulling down the statistics right? Sadly no. Twitter, the ninth largest website in the world by traffic, doesn't losslessly optimize any of their images. 33% of total page load bytes could be eliminated solely by applying lossless image optimization. Let me phrase that a different way: 1 byte of our every 3 bytes Twitter sends you is unnecessary! This is an incredible waste.

Unplowed Fields

It's clear we are not applying the image optimizations we already know about. However there is much more work to be done with images. This is a largly unresearched or unadvocated area which needs more attention.

Consider choosing the correct image format. Are people saving images as a PNG when they should be saved as a JPEG? Indeed they are. Tumblr's background image is a 76 kB PNG image and it would be 33 kB (55% smaller) if it was a JPEG. This is better than their old 827 kB PNG background image, which would be 47 kB (94% smaller) if it was a JPEG. Unfortunately I know of no other tool besides Zoompf's free performance scan which identifies PNG candidate images for conversion to JPEG.

What about JPEGs saved with a high quality setting? This is a large enough topic for its own blog post. To quickly summarize, JPEG "quality" is an arbitrary, non-linear scale, quality is not a percentage of anything, and "quality of 80" does not mean "discard 20% of graphics data." Thought leaders like Adobe recommend a quality setting of 70-80 for JPEGs published on the web. Zoompf found that 36% of Alexa Top 1000 images have a quality setting over 80, and reducing them to quality 70 would on average reduce image size by 48%! While all of these images might not be able to be reduced in quality, surely some of them can. Again, this is an area that needs more attention, more best practices and guidance, and more tools to help validate.

Not "Instead of" but "In addition too"

I am not saying other performance optimizations are not important. Zoompf checks for over 380 performance issues and we are adding more all the time. Many of them are esoteric and low impact. We flag things like duplicate cookies, unnecessary HTTP headers, and even when your <META> contains duplicate keywords. However these checks are for when you have handled all the other important checks. Image optimizations, and research into new image optimization techniques should be not done instead of other work, but in addition to it. Just remeber to prioritize what you are working on so that it will affect the most number of people in the largest possible way.

Conclusions

Images are a huge component of the web and modern web performance. This importance is only growing. Sadly, there are only one or two widely recognized image optimizations techniques. Unfortunately, these most basic optimizations are ignored, forgotten, and not uniformly applied by even the largest of websites today. Additionally, there are a lot of unexplored areas of image optimization, including lossy image optimization, with no clear recommendations or best practices and virtually no tool support. Some areas for further research include:

  • Lossy image optimizations
  • Comparison of JPEG encoders
  • PNG-to-JPEG and GIF-to-JPEG best practices, recommendations, and processes
  • Image quality for Desktop vs. mobile browsing experiences
  • Better PNG24 to PNG8 conversion guidelines. (I converted all the figures in this blog post from PNG24 to PNG8 and reduced file size by 52%)
  • Viablility of WebP and automated delivery to supported browsers

I will be discussing many of these topics this week during my presentation Take it all Off! Lossy Image Optimization at Velocity 2011 on Wednesday. I hope you all can make it.

 

Preload in visual search suggestions

Thursday, June 9th, 2011

#6 This post is part of the Velocity countdown series. Stay tuned for the articles to come.

Alrighty, this is something I've talked about last year at HighLoad++ and Fronteers, but never blogged. I came up with this thing while at Yahoo! Search and we used it there in production. So, it must be working. While probably not practical for many sites, the takeaway might very well be: optimizations are all around us, we just have to look around for them.

The summary: browsers have built-in search boxes which also give you search suggestions. IE8+ have added visual search suggestions, meaning images. The trick here is - when you don't have a visual suggestion, why not "suggest" an image for preloading, such as a sprite. Or two.

On to the details.

Chrome search

Every browser these days has them search boxes. Usually in the upper right. Like this:

(Aside rant: I call these browser search boxes "chrome search", but with the advent of the Chrome browser by Google and the fact that Google does search, this is now confusing, even doubly so. So next time you name a project or a company using a name that is already full of meaning such as Chrome or Closure, you're just being unimaginative and.. yeah, not cool. Unless of course it's something like Apple that's too far off from the actual product.)

Chrome search suggestions

Depending on how the search provider was set up, while you type the browser can send the partial string you typed to a URL of the search provider's choice. The search provider then can return a list of suggestions.

BTW, you don't have to be Google or Yahoo in order to be a search provider. This is "open search" and any site could (and frankly, should) be a search provider, here's an example how.

Visual chrome search suggestions

Starting with IE8, the provider can not only offer textual suggestions, but also visual ones, meaning images. So if you look up a stock symbol, it can give you a chart. Or a weather forecast.

The search provider sends back JSON or XML to the browser. The visual thing requires XML actually. Here's more from the horse's mouth. So in order to send an image back you use a syntax like so:

... 
<Item>
  <Text>Currently: Partly Cloudy, 67F</Text>
  <Description>High: 71F Low: 63F</Description>
  <Url>http://weather.yahoo.com/forecast/ USCA1024_f.html</Url>
  <Image source="http://path/to/my/image.png" 
         alt="Partly Cloudy" width="31" height="31"/>
</Item>
...

Preload in visual chrome search suggestions

So what if you don't have an appropriate image for a search term? Or maybe a partial term, say like for example "sch"? Why not load a sprite or some other image, hidden, 0x0 instead. Something that you can be reasonably confident the user will need once they get to the search results page. Easy-peasy:

... 
<Item>
  <Text>schmuck</Text>
  <Url>http://whatever.org</Url>
  <Image source="http://path/to/my/sprite.png" 
         width="0" height="0"/>
</Item>
...

This way you're in effect preloading something that will be needed later. I mean once the user starts typing, there's a pretty decent chance they'll end up on the results page. And they are likely to prefer a fast results page. It will be fast when pieces of it were pre-downloaded.

And if you inspect what's going on in HTTPWatch, you can see that as you type, the browser sends a request to the search provider, which then returns an XML document. And an image, although no image is seen.

What didn't work

I tried preloading CSS and JS too, but it didn't work. These show broken image icons, even with 0x0 size. I also tried 1x1 but then there's a 1x[LINE-HEIGHT] grey square/line.

And that's that

So here - a way to preload images for free, by simply using a browser feature.

You can also use this to preload 1x1 beacons for DNS resolution purposes. Say you serve search suggestions from suggest.example.org but the results are on www.example.org and the static stuff is on static1.example.org, static2.example.org, etc. There's an opportunity to pre-resolve all these domain names by requesting a tiny image from each.

Thanks for reading, this was the second post from the Velocity countdown. Only 5 more days to go! Hope you learned something, even if you don't intend to use it right away. Or maybe at least you've decided to offer your site as a search provider to all browsers. Free marketing, why not?

See you tomorrow.

 

Command-line CSS spriting

Saturday, February 19th, 2011

(In Russian)

OK, CSS sprite tools exist. I'm pretty confident I actually made the very first one :) But they break from time to time (like mine currently). And then the command line is cool (as opposed to scary) and oh-so-quick. And imagemagick is cool and oh-so-powerful. So let's see how we can create CSS sprites from the command line alone.

Creating the image

Starting with a list of separate image files:

$ ls 
1.png  2.gif  dot.png  phoney.gif  tw.gif
  • - 1.png
  • - 2.gif
  • - dot.png
  • - phoney.gif
  • - tw.gif

Creating the sprite:

$ convert *png *gif -append result/result-sprite.png

Yes, that's all! The result:

What?

So the imagemagick command is generally something like:

$ convert image1.png image2.png image3.png -append result/result-sprite.png

But we can also replace the list of images with *s:

$ convert * -append result-sprite.png

Or as in the previous case, limiting to *.gif and *.png.

How about a horizontal sprite? All it takes is changing -append to +append:

$ convert *png *gif +append result/result-sprite-horizon.png

The result:

Also note how the source images can be any format - GIF, PNG, JPEG and the result is PNG. Actually I'd recommend always trying PNG8 first:

$ convert *png *gif -append PNG8:result/result-sprite-horizon.png

CSS positions

Now since this is all hand-made there's no auto-generation of CSS. But it's still pretty straightforward. Take the vertical sprite:

All images will have background-position-x of 0px, so that's easy.

The first image will also have Y-position 0px. It also happens to be 16x16 pixels. So it's:

.first {
  width: 16px;
  height: 16px;
  background: url(result/result-sprite.png) 0 0;
}

... where 0 0 position is redundant and can be omitted.

The second image is also 16x16, that's convenient. Its X is 0 and its Y is the height of the previous image (16px) with a minus in front. So:

.secondo {
  width: 16px;
  height: 16px;
  background: url(result/result-sprite.png) 0 -16px;
}

And so on. Y position of an image is Y of the previous + the height of the previous.

You can use the handy-dandy test page to play around with this (or any other) sprite.

But.. but... figuring out dimensions by keeping track of heights? You kiddin' me?

Imagemagick to the rescue. `identify` gives you the basic image info:

$ identify 1.png 
1.png PNG 16x16 16x16+0+0 DirectClass 8-bit 260b

`identify` also has a `-format` option and supports *. So getting all the info in a neat form is easy:

$ identify -format "%g - %f\n" *
16x16+0+0 - 1.png
16x16+0+0 - 2.gif
6x6+0+0 - dot.png
10x16+0+0 - phoney.gif
16x16+0+0 - tw.gif

%f is filename and %g is geometry.
\n is a new line as you would expect and sometimes - is just a -.
So if you want to figure out the Y position of the fifth element, well, it's the sum of the heights of the previous: 16+16+6+16

.last {
  width: 16px;
  height: 16px;
  background: url(result-sprite.png) 0 -54px
}

Some complicated math! 'scuse me while I ask my second grader if she can handle it :)

And some smushing

Imagemagick doesn't write optimal PNGs. So some optimization is due. You can do it yourself with pngout, optipng, etc. Or use web-based tools such as smush.it (you're welcome!) or punypng.com. (psst - how bout a glimpse of the past)

Or how about.... smush.it on the command line:

$ curl http://www.smushit.com/ysmush.it/ws.php
       ?img=http://www.phpied.com/files/sprt/result/result-sprite.png

Result is JSON:

{"src":"http:\/\/www.phpied.com\/files\/sprt\/result\/result-sprite.png",
 "src_size":1759,
 "dest":"http:\/\/smushit.zenfs.com\/results\/5a737623\/smush\/%2Ffiles%2Fsprt%2Fresult%2Fresult-sprite.png",
 "dest_size":1052,
 "percent":"40.19",
 "id":""}

Oh looky, almost half the filesize. Let me at it! Copy the `dest` URL:

$ curl http:\/\/smushit.zenfs.com\/results\/5a737623\/
       smush\/%2Ffiles%2Fsprt%2Fresult%2Fresult-sprite.png > result/smushed-sprite.png

And that's that.

Recap

  1. create image:
    $ convert *png *gif -append PNG8:result/result-sprite.png
  2. get dimensions:
    $ identify -format "%g - %f\n" *png *gif
    
  3. optimize:
    $ curl http://www.smushit.com/ysmush.it/ws.php?img=http://url...
    

Test page to play with the result-sprite is here.

For some more ideas and a different imagemagick command for generating sprites - see the very original post announcing the csssprites.com.

 

Inline MHTML+Data URIs

Sunday, October 3rd, 2010

MHTML and Data URIs in the same CSS file is totally doable and gives us nice support for IE6+ and all modern browsers. But the question is - what about inline styles. In other words can you have a single-request web application which bundles together markup, inline styles, inline scripts, inline images? With data URIs - yes, clearly. But MHTML?

I remember hacker extraordinaire Hedger Wang coming up with a test page, which proved it's doable. Problems with the test are that a/ I can't find the page anymore, his domain has expired b/ there was some funky IE7/Vista stuff (probably now solvable) in there which even included an undesired redirect c/ was complex - the whole HTML becomes a multipart document, if I remember correctly there was something that required html served as text/plain....

So I tried something simple - shove an MHTML doc inside an inline style comment. It so totally worked! Including IE6 and IE8 in IE7 mode on Windows 7 (which in my experience behaves as badly as IE7 proper on Vista)

Here's the test page. Look ma', no extra HTTP requests :)

So it's a simple HTML doc:

<!doctype html>
<html>
  <head>
    <title>Look Ma' No HTTP requests</title>
    <style type="text/css">
 
/* magic here */
 
    </style>
  </head>
  <body>
    <h1>MHTML + Data:URIs inline in a <code>style</code> element</h1>
    <p class="image1">hello<br>hello</p>
    <p class="image2">bonjour<br>bonjour</p>
  </body>
</html>

And the magic is two parts: the MHTML doc inside a CSS comment and the actual CSS which uses data URIs for normal browsers and refers to the MHTML parts in IE6,7.

/*
Content-Type: multipart/related; boundary="_"
 
--_
Content-Location:locoloco
Content-Transfer-Encoding:base64
 
iVBORw0KGgoAAAAN ... [more crazyness]... QmCC
--_
Content-Location:polloloco
Content-Transfer-Encoding:base64
 
iVBORw0KGgoAAAANSUh ... [moarrr] ... ggg==
--_--
*/
.image1 {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAAN ... QmCC"); 
  *background-image: url(mhtml:http://phpied.com/files/mhtml/mhtml-html.html!locoloco); 
}
 
.image2 {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUh ... ggg=="); 
  *background-image: url(mhtml:http://phpied.com/files/mhtml/mhtml-html.html!polloloco); 
}
 
body {
  font: bold 24px Arial;
}

How cool is that!

Please report any issues you might find in any browser/os combination

The obvious drawback is repeating the long base64'd image content twice, but it's solvable with either server-side sniffing or... one crazy hack, found on the Russian site habrahabr.ru. I should talk about it separately and help spread the word to the larger English-speaking audience, but for the impatient - click!

So there you go - MHTML inline in CSS inline in HTML or building single-request x-browser web apps :)

 

The proper MHTML syntax

Sunday, October 3rd, 2010

Reducing the number of HTTP requests is a must, sprites are cool, but a pain to maintain, so there come data URIs (for all browsers) and MHTML (IE6 and 7). I've talked about these things on this blog to a point where the blog comes up in top 10 results in search engines for queries like "mhtml" and "data url". Therefore I think it's my duty to clarify a point for the good of the mankind :)

MHTML works in IE6 and IE7 even in the deadly IE7/Vista and IE7/Win7 combos

In the community we've long considered MHTML in IE7/Vista a problem and I've personally come up with complex voodoo workarounds how to mitigate the issue and still make use of the technique. Turns out the whole problem all the time was a small syntax glitch.

Pointed by a comment at a previous post all we ever needed was to close the boundary delimiter and add two dashes at the end. The double-dash of doom as I like to call them since I've spent so much time wrestling.

So let's take a look at the syntax.

Update: Also check this comment for additional insight from Vincent, Aaron and _cphr_ regarding double line break of doom

One part

MHTML is a multi-part document. One document containing several parts. One part looks like this:

Content-Location: myimage
Content-Transfer-Encoding: base64

iVBORw0KGgoAAAANSU....U5ErkJggg==

In other words it has headers, base64-encoded content and two empty lines to divide them.

Multi parts

The different parts in the document are divided by a separator string. And at the top of the document you define what this separator is. Anything you like. So

Content-Type: multipart/related; boundary="MYSEPARATOR"

--MYSEPARATOR

[here comes part one]

--MYSEPARATOR

[here's part two]

--MYSEPARATOR--

Did you notice -- at the very end? Yes, this is the double-dash of doom. Forget it and you get IE7/Vista problems (only on cached documents) and permanent hair loss. The thing is that in IE6 and other IE7s you can omit the whole last separator and it's all good. So historically you never needed it, but come Vista and Win7 and problems start.

All together now

Finally, let's see the whole thing, a whole CSS file, including the way you refer to the parts later on in the CSS.

/*
Content-Type: multipart/related; boundary="MYSEPARATOR"
 
--MYSEPARATOR
Content-Location: myimage
Content-Transfer-Encoding: base64
 
iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAD....U5ErkJggg==

--MYSEPARATOR
Content-Location: another
Content-Transfer-Encoding: base64
 
iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAA....U5ErkJggg==

--MYSEPARATOR--
*/
.myclass {
    background-image:url(mhtml:http://example.org/styles.css!myimage);
}
.myotherclass {
    background-image:url(mhtml:http://example.org/styles.css!another);
}

Updated PHP class

Previously when I fought IE7/Vista I came up with a PHP class that would take some images and create "data sprites" on the fly, creating two different versions - one with data URIs and one with MHTML, depending on the browser. The old code is here.

Now, I've updated it (basically just deleted serious portions of it that dealt with Vista) and put it up on github. Right here.

Updated test pages

Thanks for reading, that's about it. Now off I go to correct the older posts, catch ya later :)

 

15 minutes could save you…

Monday, May 17th, 2010

Since I have a ton of things to do, I decided it was about time to spend some time with this blog, performance optimization-wise. Not do anything special, just the bare minimum, the no-brainer, works-every-time, easy stuff. And I'm quite happy with the results.

I only looked at the homepage, although the results will be seen throughout the site. Unfortunately there was a youtube video on the homepage, otherwise the results would have been even better, KB-wise

gzip on

First things first - turning compression on. I've previously whined about the host of this blog, site5.com and how I wasn't able to enable compression for some tests and had to make PHP do the compression. Turned out, all it takes is just ask and open a support ticket. Second level support decided to compile Apache with mod_deflate and I was good to go. I put this in .htaccess:

AddOutputFilterByType DEFLATE text/css text/plain text/xml application/javascript application/json

This blog has no JavaScript, just HTML and CSS. The HTML became 5K (from 23K) and CSS became 3 something (from 11K)

Flush

Decided to go fancy here and do first byte flush early on. That, of course, can be problematic, especially on shared hosting. I mean problematic is to have gzip working together with flushing (tips). Eventually I gave up wrestling with .htaccess and php.ini settings and let PHP handle it. That's why you may notice that text/html is missing from the DEFLATE list above.

So all it took was going to my WordPress theme, finding something called header.php and adding two lines of PHP.

One at the top:

<?php ob_start("ob_gzhandler"); ?>
<!DOCTYPE html ...

And one at the bottom:

<?php ob_flush(); flush(); ?>

Now the header part is flushed in one chunk and the rest in another. Check it in chunkview...

favicon.ico

I have no favicon so I get a lot of 404s, since browsers insist on downloading this little thing. So I created one. Took a social profile photo, croped (Option+K) and played with green colors in Mac's built-in Preview program. Then, ImageMagick:

$ convert -resize 16x16 dude.png PNG8:favicon16.png
$ convert favicon16.png favicon.ico

FTP. Done. No mo' 404s for favicon.

Minifying CSS

Copy-paste into CSSMin and CSS lost 30% of its weight. Now after gzipping and minifying, the CSS went from total 11.3K to 2.6K.

Cover images

I have some book covers on the homepage. One was simply linked to the publisher's site (extra DNS, connection...). Also turned out the publisher has redesigned the site so there was also a redirect. Disaster. Also the image was a 26K PNG where it could be 4k JPEG.

$ convert cover.png cover.png.jpg
$ jpegtran -copy none -optimize cover.png.jpg > cover.jpg

And just like that - 4 book covers were optimized.

Before/after

And that's all I did. I can probably do Expries headers too, convert/sprite all smiley GIFs and so on but that means touching more of WordPress, so I stopped here.

Now the page loads (onload) in 1.2 seconds, down from 2.2 (45% faster).

There are fewer DNS lookups, no 404s, no 301s

Page weight is down from 285K to 186K (34%). Actually if you exclude the youtube 142K SWF, the result is: 143K (before) to 44K (after) or a page weight saving of 70%. Not bad, not bad at all.

And the waterfalls. Before:

After:

PageSpeed score only went from 84/100 to 87/100 which was not impressive at all. I think 84K may have been too generous, but maybe not, given how worse sites there are out there.

So this is it - 15 minutes could save you 45% page load time and 70% download sizes :)

 

Preload CSS/JavaScript without execution

Wednesday, April 21st, 2010

Preloading components in advance is good for performance. There are several ways to do it. But even the cleanest solution (open up an iframe and go crazy there) comes at a price - the price of the iframe and the price of parsing and executing the preloaded CSS and JavaScript. There's also a relatively high risk of potential JavaScript errors if the script you preload assumes it's loaded in a page different than the one that preloads.

After a bit of trial and lot of error I think I came up with something that could work cross-browser:

  • in IE use new Image().src to preload all component types
  • in all other browsers use a dynamic <object> tag

Code and demo

Here's the final solution, below are some details.

In this example I assume the page prefetches after onload some components that will be needed by the next page. The components are a CSS, a JS and a PNG (sprite).

window.onload = function () {
 
    var i = 0,
        max = 0,
        o = null,
 
        // list of stuff to preload
        preload = [
            'http://tools.w3clubs.com/pagr2/<?php echo $id; ?>.sleep.expires.png',
            'http://tools.w3clubs.com/pagr2/<?php echo $id; ?>.sleep.expires.js',
            'http://tools.w3clubs.com/pagr2/<?php echo $id; ?>.sleep.expires.css'
        ],
        isIE = navigator.appName.indexOf('Microsoft') === 0;
 
    for (i = 0, max = preload.length; i < max; i += 1) {
        
        if (isIE) {
            new Image().src = preload[i];
            continue;
        }
        o = document.createElement('object');
        o.data = preload[i];
        
        // IE stuff, otherwise 0x0 is OK
        //o.width = 1;
        //o.height = 1;
        //o.style.visibility = "hidden";
        //o.type = "text/plain"; // IE 
        o.width  = 0;
        o.height = 0;
        
        
        // only FF appends to the head
        // all others require body
        document.body.appendChild(o);
    }
    
};

A demo is here:
http://phpied.com/files/object-prefetch/page1.php?id=1
In the demo the components are delayed with 1 second each and sent with Expries header. Feel free to increment the ID for a new test with uncached components.

Tested in FF3.6, O10, Safari 4, Chrome 5, IE 6,7,8.

Comments

  • new Image().src doesn't do the job in FF because it has a separate cache for images. Didn't seem to work in Safari either where CSS and JS were requested on the second page where they sould've been cached
  • the dynamic object element has to be outside the head in most browsers in order to fire off the downloads
  • dynamic object works also in IE7,8 with a few tweaks (commented out in the code above) but not in IE6. In a separate tests I've also found the object element to be expensive in IE in general.

That's about it. Below are some unsuccessful attempts I tried which failed for various reasons in different browsers.

Other unsuccessful attempts

1.
I was actually inspired by this post by Ben Cherry where he loads CSS and JS in a print stylesheet. Clever hack, unfortunately didn't work in Chrome which caches the JS but doesn't execute it on the next page.

2.
One of the comments on Ben's post suggested (Philip and Dejan said the same) using invalid type attribute to prevent execution, e.g. text/cache.

var s = document.createElement('script');
s.src = preload[1];
s.type = "text/cache";
document.getElementsByTagName('head')[0].appendChild(s);

That worked for the most parts but not in FF3.6 where the JavaScript was never requested.

3.
A dynamic link prefetch didn't do anything, not even in FF which is probably the only browser that supports this.

for (i = 0, max = preload.length; i < max; i += 1) {
    var link = document.createElement('link');
    link.href = preload[i];
    link.rel = "prefetch";
    document.getElementsByTagName('head')[0].appendChild(link);
}

Then it took a bit of trial/error to make IE7,8 work with an object tag, before I stumbled into IE6 and gave up in favor of image src.

In conclusion

I believe this is a solution I could be comfortable with, although it involves user agent sniffing. It certainly looks less hacky than loading JS as CSS anyways. And object elements are meant to load any type of component so no semantic conflict here I don't believe. Feel free to test and report any edge cases or browser/OS combos. (JS errors in IE on the second page are ok, because I'm using console.log in the preloaded javascript)

Thanks for reading!

 

IE9 and JPEG-XR: first impressions

Monday, April 5th, 2010

One of the new features in IE9 is the support for the JPEG-XR format, which reportedly has a better compression. Is it something we should dive into ASAP?

JPEG-XR

The wikipedia article is here. This format is developed and patented (red flag!) by Microsoft (yellow flag! :) ), it replaces the suggested JPEG-2000 format and is an official standard as of mid '09. It's formerly known as "HD photo" and "Microsoft something something" and is heavily used in Vista and Windows 7 - two OSes I'm yet to experience. Anyway.

The wikipedia article says that the format has a better compression, so I had to take a look at it!

Software support

That's where the problems start. The list of software that supports the format is not too long and most of the software just reads it, like IE9. IE9 doesn't run on XP, so I couldn't actually test it.

What I managed to try was:

  • a plugin for Paint.NET that read/writes JPEG-XR
  • a 60-day trial version of MS' Expression Design 3 that reads/writes - this required an upgrade of my .Net platform, so the whole process took forever on the poor XP VMWare (but hey, I have a book to finish, so no distraction is too long!)
  • a plugin for IrfanView that reads the format

Nothing for Mac though.

There's rumors all over the place that MS have a beta version of a Photoshop plugin that works on the Mac, but even the MS' press release linked to a 404. (which redirected to a bing search - here's the secret to gaining search market share!)

Actually before I digged into these programs, my first instinct was to check if ImageMagick supports this format. Turns out no. IMagick used libjpeg like pretty much all image programs out there and curiously enough here's what libjpeg's README has to say:

FILE FORMAT WARS
================

The ISO JPEG standards committee actually promotes different formats like
JPEG-2000 or JPEG-XR which are incompatible with original DCT-based JPEG
and which are based on faulty technologies.  IJG therefore does not and
will not support such momentary mistakes (see REFERENCES).
We have little or no sympathy for the promotion of these formats.  Indeed,
one of the original reasons for developing this free software was to help
force convergence on common, interoperable format standards for JPEG files.
Don't use an incompatible file format!
(In any case, our decoder will remain capable of reading existing JPEG
image files indefinitely.)

Sounds like another red flag to me. IDG (Independent JPEG Group) are the creators of libjpeg. If libjpeg doesn't sounds like it will support it JPEG-XR, that means adoption can be really slow if not feasible at all. But even if IE is the only browser under the sun that supports the format and the format is so much better, then there might be cases where it could be beneficial to browser-sniff and send different image versions, as far-fetched as that may sound.

Test in Paint.NET

I started with Paint.NET because it was the easiest. I took a photo I've taken with the iPhone, keeping the use case real, and resized to a 600x450px which sounds like a normal thing to do in a blog post for example. I used IrfanView and PNG, so that the original is lossless (click the thumb for the actual source image).

I converted the photo with Paint.NET to JPEG and to JPEG-XR (also called WDP/HD Photo). In both cases I used quality of 80%. There was also an option for WDP which was 32bit image by default, which I changed to 24 bits because the image was smaller filesize.

WDP export in Paint.NET

The results were - 45K for XR/WDP and 24K for JPEG. So the good old JPEG was smaller - the exact opposite of what should've happened. Additionally JPEGTran shaved off another 1.3K from the file. Seemed like JPEG-XR is not that good after all. But as I said I had a book to write so I kept going with the distractions, determined to avoid writing for as long as I can.

Test with Expression Design

Expression Design produced the exact same WDP/HD Photo/JPEG-XR file - 45K. And this is not surprising actually, since there is an image framework from MS, called WIC, part of .Net, which is probably what Paint.NET and Expression Design both use. But surprisingly enough the JPEG outcome from Expression Design was significantly bigger - 57K. What?!

Then I looked at the visual quality and the number of colors and it turned out the JPEGs were pretty different, although they were converted from the same PNG and using 80% in both programs.

Software/Format JPEG JPEG-XR aka WDP aka HD Photo
Paint.NET 24K (50 000+ colors) 45K (104 000+ colors)
Expression Design 57K (54 000+ colors)

Visually the JPEG from Paint.NET is clearly lower quality than the one from Expression Design and from the WDP format. Interestingly, IrfanView produced an pretty much identical file when converting the PNG to JPEG with quality 80. So Expression Design seems to be doing something differently.

Using IrfanView I increased the quality of the JPEG until the file size reaches the file size of the WDP. (After all, all I want to know is which format has the smaller filesize). The quality of 93 resulted in a JPEG that was about the same file size as quality 80 JPEG-XR. Then I tried so look at the visual quality and although I'm not a designer, it seemed to me that the two images are pretty identical and XR is maybe just a little better. But that's a little subjective.

Here's the two files for comparison. Let me know which one you think is better. In this case they are both losslessly converted to PNG, so all browsers can see the WDP.

Here's also an image diff (from ImageMagick's compare) - it shows that technically the two images are very different (the white dots are pixels with the exact same color values)

One other thing about Expression Design - when exporting WDR, it has a "transparency" checkbox ON. This results is bigger images, so make sure you turn it off when using, it makes no sense for photos.
Expression Design options

Batch conversion?

My motivation in this experiment was to see if there's a way (and a reason) to do a batch conversion of all JPEG imagery to JPEG-XR. This would be my favorite performance optimization - you run one script and wake up to a 5-10% less image bandwidth.

Looks like JPEG-XR could probably look better for the same filesize, meaning maybe a smaller filesize for the same quality. But it's not easy to decide when it comes to quality and certainly even harder for a machine (a simple batch conversion script) to tell. I was hoping that there's a way to losslessly convert to JPEG-XR. From what I can see, there isn't. JPEG-XR does have a lossless option but it creates huge files (like 250K instead of 45), so the lossless versions are not meant to be on the web. BTW, the lossless option is the same as 100 quality (which is not the same in normal JPEG, where even 100% is lossy).

So, in conclusion - JPEG-XR may look promising but is currently unusable for practical purposes, because of

  • the extremely limited support in browsers (browser, actually),
  • very limited choices of creation software
  • the benefits are hard to distinguish
  • not possible to batch-and-forget process all old JPEGs

And there's the other turn off - patents. Although Microsoft has promised to promise not to sue people around for implementing JPEG-XR, a patent is a patent and all software patents must die on general principle :)

 

Uncompressed data in base64? Probably not

Thursday, February 4th, 2010

The beauty of experimentation is that failures are just as fun as successes. Warning: this post is about a failure, so you can skip it altogether :)

The perf advent calendar was my attempt to flush out a bunch of stuff, tools and experiments I was doing but never had the time to talk about. I guess 24 days were not enough. Here's another little experiment I made some time ago and forgot about. Let me share before it disappears to nothing with the next computer crash.

I've talked before about base64-encoded data URIs. I mentioned that according to my tests base64 encoding adds on average 33% to the file size, but gzipping brings it back, sometimes to less than the original.

Then I saw a comment somewhere (reddit? hackernews?) that the content before base64-encoding better be uncompressed, because it will be gzipped better after that. It made sense, so I had to test.

"Whoa, back it up... beep, beep, beep" (G. Constanza)

When using data URIs you essentially do this:

  1. take a PNG (which contains compressed data),
  2. base64 encode it
  3. shove it into a CSS
  4. serve the resulting CSS gzipped (compressed)

See how it goes: compress - encode - compress again. Compressing already compressed data doesn't sound like a good idea, so it sounds believable that skipping the first compression might give better results. Turns out it's not exactly the case.

Uncompressed PNG?

The PNG format contains information in "chunks". At the very least there's header (IHDR), data (IDAT) and end (IEND) chunks. There could be other chunks such as transparency, background and so on, but these three are required. The IDAT data chunk is compressed to save space, but it looks like it doesn't have to be.

PNGOut has an option to save uncompressed data, like
$ pngout -s4 -force file.png

This is what I tried - took several compressed PNGs, uncompressed them (with PNGOut's -s4), then encoded both with base64 encoding, put them in CSS, gzip the CSS and compared file sizes.

Code

<?php
// images to work with
$images = array(
  'html.png',
  'at.png',
  'app.png',
  'engaged.png',
  'button.png',
  'pivot.png'
);
//$images[] = 'sprt.png';
//$images[] = 'goog.png';
//$images[] = 'amzn.png';
//$images[] = 'wiki.png';
 
// css strings to write to files
$css1 = "";
$css2 = "";
 
foreach ($images as $i) {
  
  // create a "d" file, d as in decompressed
  copy($i, "d$i");  
  $cmd = "pngout -s4 -force d$i";
  exec($cmd);
  
  // selector
  $sel = str_replace('.png', '', $i);
 
  // append new base64'd image 
  $file1 = base64_encode(file_get_contents($i));
  $css1 .= ".$sel {background-image: url('data:image/png;base64,$file1');}\n";
  $file2 = base64_encode(file_get_contents("d$i"));
  $css2 .= ".$sel {background-image: url('data:image/png;base64,$file2');}\n";
 
}
 
// write and gzip files
file_put_contents('css1.css', $css1);
file_put_contents('css2.css', $css2);
exec('gzip -9 css1.css');
exec('gzip -9 css2.css');
 
?>

Results

I tried to keep the test reasonable and used real life images - first the images that use base64 encoding in Yahoo! Search results. Then kept adding more files to grow the size of the result CSS - added Y!Search sprite, Google sprite, Amazon sprite and Wikipedia logo.

test with compressed PNG, bytes with uncompressed PNG, bytes difference, %
Y!Search images 700 1506 54%
previous + Y!Search sprite 5118 8110 36%
previous + Google sprite 27168 40836 33%
previous + Amazon sprite + Wikipedia logo 55804 79647 29%

Clearly starting with compressed images is better. Looks like the difference becomes smaller as the file sizes increase, it's possible that for very big files starting with uncompressed image could be better, but shoving more than 50K of images inline into a CSS file seems to be missing the idea of data URIs. I believe the idea is to use data URIs (instead of sprites) for small decoration images. If an image is over 50K it better be a separate request and cached, otherwise s small CSS tweak will invalidate the cached images.

 

One-click Minifier Gadget (OMG) – initial checkin

Sunday, January 31st, 2010

So I've been thinking and talking to folks about this idea of having one-stop shop for all your minification needs. Minification of JS and CSS as well as image optimization helps site performance by reducing download sizes. This is good. But not a lot of people do it.

People don't do it, because it's a PITA :) It's simple enough, but with deadlines upon you and all that, you don't want to do an extra step. That's why having a build process helps, by automating this. But setting up a build process is yet another PITA. So it goes.

So my idea was to help busy designers and developers, that wouldn't invest their time researching which minifiers are good, downloading setting up, learning about the 10+ PNG optimization tools... That's how the the idea for the one-click OMG tool came about. (One-drag is more appropriate, come to think of it...) One tool that runs on all operating systems - Win, Mac, Linux - and delivers all minification and optimization tools you need as one package.

Running

Running the tool is as simple as drag/dropping a bunch of files and directories. Here I've dropped "wordpress" directory. The tool recursively looks into the dropped files for things it can optimize. More information here.

OMG screenshot

Download

Version 0.0.1 is here. It doesn't do image optimization, only JS and CSS minification, but please feel free to download and give it a shot. Unzip the package for your OS and run omg.exe (Windows), OMG.app (Mac), or the omg binary (Linux)

Open source

The code is on GitHub. Fork and enjoy.

The developer's notes are there too - how to setup, run, package. Also a list of todos if you want to help.

Next?

This is just a preliminary version. Feel free to join, comment, suggest. Hate the name? Say so :)

Personally, looks like my plate is very full for the next moth or two, so I probably won't be actively working on the tool. I hope though the foundation is good enough and relatively documented, should be easy to pick up if anyone's interested in contributing.

Built with XUL

This has been a learning experience for me with XULRunner. I loved it. I love the idea of being able to create cross-OS desktop apps with JavaScript alone.

Behind the scenes, I'm using my JavaScript port of YUICompressor's CSSmin and Doug Crockford's JSMin. JSMin should be replaced with YUICompressor (or Google closure compiler) in the next release.

 

CSS performance: UI with fewer images

Wednesday, December 23rd, 2009

2010 update:
Lo, the Web Performance Advent Calendar hath moved

Dec 23 This post is the one-before-last article in the 2009 performance advent calendar experiment.

Often performance improvements come with their drawbacks, sometimes improving performance causes pains in other parts of the development process or strips stuff from the final product. Sometimes there's even a conflict where you have to pick: slow, unusable and beautiful or fast and looking like hacked with a blunt axe. But it doesn't have to be this way.

This post outlines some approaches to achieving common UI elements using CSS tricks in as many browsers as possible while using as fewer images as possible. Some of the tricks are brand new, some are very, very old, IE5.5. old. They all have in common the "fewer or no images" mantra. Using fewer images comes with some pronounced benefits:

  • less time spent in Photoshop
  • lighter page, less HTTP requests, less image payload
  • fewer elements in the sprite to maintain (and sometimes fewer sprites) which means longer lived sprites with fewer updates and cache invalidation
  • generally easier maintenance - it's much easier to change a color value than to update and push a new image version

Sometimes some browsers may not be fully supported but that's ok - as long as there's progressive enhancement and the basic page is usable, people rarely notice 1px glows and other ornaments.

So let's get started. BTW, a test page with the stuff discussed in the post is here.

Rounded corners

Yep, let's tackle the biggie.

Forget rounded corners in browser that don't support border-radius. Period. It may be hard to argue this case, but definitely try. Doing rounded corners any other way than border-radius is a pain - it adds markup bloat, it makes you create more images or sprite elements. It's tougher to update. Just forget it. Forget rounded corners in IE < 9 (as rumor has it border-radius is coming to IE9). People may argue that IE is important for your audience. No doubt that's true, but rounded corners are not so important for the audience. Show your designer Yahoo Search results page - the sidebar on the left-hand side. Not very rounded in IE. Do you think this was an easy battle - losing rounded corners in IE for such a high-profile site? Ask the man who won the battle ;)

So starting with a normal module - head, body and border:

The markup - nice and clean:

  <div class="module">
    <div class="hd"><h3>This is the header</h3></div>
    <div class="bd">
      <p>Here comes the content</p>
      <p>Here comes some more</p>
      <p>You can never have too much content, because
         content is king, right?
      </p>
 
    </div>
  </div>

Some fairly simple border radius to support Firefox, Webkit (Safari, Chrome, iPhone...) and, since a few days ago, Opera 10.5 alpha:

.module {
  -moz-border-radius: 9px; 
  -webkit-border-radius: 9px; 
  border-radius: 9px;
}

Result:

This is it! Easy-peasy, lemon squeezy.

Now, it's a little annoying to write three declarations for the same thing, but, hey - beats images and extra markup hands down. Also annoying are the differences when setting individual corners (-moz-border-radius-topleft is -webkit-border-top-left-radius). In this case we need to also round the header (class .hd) so it doesn't bleed through the beautifully rounded corners:

.hd {
  -moz-border-radius: 8px 8px 0 0; 
  -webkit-border-top-left-radius: 8px; 
  -webkit-border-top-right-radius: 8px; 
  border-radius: 8px 8px 0 0;
}

Verdict:

  • Full support: Firefox, Safari, Chrome, Opera 10.5
  • Fallbacks: IE (corners are not rounded)

Drop shadows and glows

Another favorite effect designers love - dropping shadows. It's easy to enhance that existing .module without any new images:

.module {
  /* offset left, top, thickness, color with alpha */
  -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5); 
  -moz-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5); 
  box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
  /* IE */
  filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=5, OffY=5, Color='gray');
  /* slightly different syntax for IE8 */
  -ms-filter:"progid:DXImageTransform.Microsoft.dropshadow(OffX=5, OffY=5, Color='gray')";
}

And now our module casts a shadow:

Now two notes for IE: first the shadow doesn't have alpha so it's not as nice and second, this filter may not play along with other filters in the same module. But the shadow is cast and that's a check for IE too, even IE5.5!

You may notice that in this case we basically need to more or less repeat the same declaration three times and the IE declaration two times. This is irksome, but hopefully keeping the strings close together should help gzip compression.

As for glowing, it's the same thing in FF, Webkit, Opera, only without any offset. For IE, there's a different filter called glow:

.glow {
  -webkit-box-shadow: 0 0 10px rgba(50, 50, 50, 0.8);
  -moz-box-shadow: 0 0 10px rgba(50, 50, 50, 0.8); 
  box-shadow: 0 0 10px rgba(50, 50, 50, 0.5);
  filter:progid:DXImageTransform.Microsoft.glow(Strength=5, Color='gray');
  -ms-filter:"progid:DXImageTransform.Microsoft.glow(Strength=3, Color='gray')";
}

I added these declaration to a new class .glow so I can add the class name to modules that need to glow. The result:

The result as it glows in IE:

Now you see why I added only 3 pixels glow in IE and whole 5 in the rest. The IE glow is a little .. interesting. Also in IE8 (could be my VM, in IE6 XP no VM all looks OK) the glow seems to move slightly when you hover over the module.

Verdict for shadows and glows:

  • Full support: FF, Safari, Chrome, Opera, IE5.5 and up

More info:

Gradients

Ah, gradients. Sometimes so subtle that we, muggles and other mere mortals, don't see them even when we try our hardest. But for the designer they could be life/death situation.

Let's make the head (class .hd) of our module a gradient without any images:

.hd {background-image: -moz-linear-gradient(top, #641d1c, #f00); 
  background-image: -webkit-gradient(linear, left top, left bottom, from(#641d1c), to(#f00));
  filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#ff641d1c,endColorstr=#ffff0000);
  -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#ff641d1c,endColorstr=#ffff0000)";
}

The result:

What a beautiful (code-speaking, of course, not so sure about visually beautiful) module. It has rounded corners, drop shadows and a gradient and so far we haven't used even a single image. Which means this reddish module can become blue, green or, god forbid, pink - with a single tweak in the code, the CMS or the user preferences (if you're building a social network for example).

Gradients verdict:

  • Full support: FF, Safari, Chrome, IE
  • Fallbacks: Opera (solid color)

More info:

... and RGBA for all

Being able to set the transparency of the background without affecting the transparency of the foreground (the text) is quite handy. That's why there's rgba() in CSS (red, green, blue, alpha). IE is not yet supporting it, but we can use the gradient filter which does support transparency. In this case we don't need the actual gradient so we set start and end color to the same thing. Also the background: transparent is needed for the whole thing to work in IE:

.rgba {
  background-color: transparent;
  background-color: rgba(200,200,200,0.8);
  filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99dddddd,endColorstr=#99dddddd);        
  -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99dddddd,endColorstr=#99dddddd)";
}

The result is pleasantly cross-browser:

RGBA verdict

  • Full support: Firefox, Safari, Opera, Chrome, IE

Rotating images

It happens that sometimes you use the same image only flipped. For example open/close thingies, menus and such. How about reusing the same image and rotating it with CSS?

.arrow {background: url(arrow.png) no-repeat; display: block; float: left; width: 33px; height: 33px;}
.right{ /* this is the original image*/ }
.left {
  -moz-transform: rotate(180deg);-webkit-transform: rotate(180deg); -o-transform: rotate(180deg); 
  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
  -ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";}
.up {
  -moz-transform: rotate(270deg);-webkit-transform: rotate(270deg); -o-transform: rotate(270deg); 
  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
  -ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";}
.down {
  -moz-transform: rotate(90deg);-webkit-transform: rotate(90deg); -o-transform: rotate(90deg); 
  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
  -ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
}

Here's the result. Single image:

Arrow

Result:

the HTML:

<span class="arrow right"></span>
<span class="arrow left"></span>
<span class="arrow up"></span>
<span class="arrow down"></span>

You may notice that the CSS could be quite verbose for saving such small images. It's highly recommended you add the rotation code to a class and use the class name when necessary instead of repeating the same long declaration for every use case or image. Then pray to the gods of compression that this thing gzips well ;)

Verdict

  • Full support: Firefox, IE, Safari, Opera, Chrome

Multiple UI elements with the same background image

The last few tricks have something in common - they each use one background image. The background images are very small - usually around 100 bytes. The tiny image has some transparency to it and is placed as a background-image which sits on top of a background-color. Because of the transparency, the background color shines through, but differently depending on the transparency level of the image above it.

The result is - different UI elements with different colors (and even different hover colors) which can be part of CMS or part of user's skinning and they all reuse the same tiny background. So what can we do this way? A lot of interesting background effects, but here's a few.

Glossy buttons

Here's the end result:

All these buttons share the same background image. The image is 1x1000 and repeated horizontally. The 1000 is just to be safe, very safe, because 50, 100 or 1000 doesn't affect the file size which just a mere 100 bytes. The upper half of the image is a little less transparent. The lower half is 100% transparent. When placed on top of the solid color the whole thing looks shiny and glossy. And you can change the color any way you like.

The HTML:

<p class="button">button1<p>
<p class="button button2">button2<p>
<p class="button button3">button3<p>
<p class="button button4">button4<p>
<p class="button button5">button5<p>
<p class="button button6">button6<p>

And the CSS can't be simpler:

.button {
  background-image:url(http://tools.w3clubs.com/mask/mask.php?x=1000&type=h);
  background-position: center;
}
.button:hover {background-color: #F29222;}
.button2 {background-color: #A41D1C;}
.button3 {background-color: #0F6406;}
.button4 {background-color: #333f79;}
.button5 {background-color: black;}
.button6 {background-color: orange; color: black;}

Actually in the test page I have inlined the image with data URI to save the whole HTTP request for such a teeny image.

As you can see in the URL of the background, I've done a little script to generate some background images:
http://tools.w3clubs.com/mask/mask.php?x=1000&type=h
The image generator's source code is right here.

Stripes

Same technique - but used to generate striped background:

It's basically the same code, only using a different call to the image generator to give us a different background image.

HTML:

  <div class="module stripe earth glow">
    <div class="bd">
      <p>striped background</p>
    </div>
 
  </div>
 
  <div class="module stripe tech glow">
    <div class="hd phony stripe"><h3>stripity-stripes</h3></div>
    <div class="bd">
      <p>striped background with the same background image</p>
    </div>
  </div>

CSS:

.stripe {background-image: url(http://tools.w3clubs.com/mask/mask.php?type=stripe);}
.earth  {background-color: olive;}
.tech   {background-color: #bbb;}
.phony  {background-color: #0F6406;}

Again, this image can be a data URI so we save the single HTTP request.

And another gradient

So if you don't like the previously discussed way to do gradients, here's another one. The same trick with the solid color background and a semi-transparent image on top.

Result:

The background images as generated by the service are:
// lighter at the top
http://tools.w3clubs.com/mask/mask.php?type=gradient
// darker at the top
http://tools.w3clubs.com/mask/mask.php?type=gradient&flip=1

Again, you can see the test page here and the source for the image generation is here.

For yet another example of this technique check my post on this (abandoned) blog phonydev.com. There I take an image and a mask image generated by the same script and overlay to achieve an iPhone-like glossy button.

iphone glossiness

Thanks!

Kind of long post, but I hope you're excited about removing a bunch of images from your future designs. If I've omitted some details, please let me know in the comments.

 

Give PNG a chance

Sunday, December 13th, 2009

2010 update:
Lo, the Web Performance Advent Calendar hath moved

Dec 13 This post is part of the 2009 performance advent calendar experiment. Stay tuned for the articles to come.

People are often afraid to use PNG because they think that:
a/ it doesn't work in all browsers, or
b/ filesizes are bigger than GIF

While these have some grain of truth to them, they are mostly misconceptions. Before addressing them, one quick background point - what's PNG8 and why it's cool.

PNG8

There are several types of PNG files, which can be grouped into those main kinds:

  • Truecolor PNGs with or without alpha transparency channel, also known as PNG24 and/or PNG32 (the one with alpha)
  • Grayscale PNGs with or without alpha
  • Indexed PNG, aka palette PNG, aka PNG8

PNG8 is like a GIF - it has a palette of 256 colors and supports transparency. While GIF supports true/false transparency (a pixel is either transparent or it isn't), PNG8 supports variable alpha transparency. Right there you see - PNG8 can do anything that GIF can, plus more.

There's a little glitch in IE6 where a semi-transparent pixels in PNG8 are seen as fully transparent, just like a GIF. So here's an option for progressive enhancement - you use the same image and IE6 gets a degraded GIF-like experience, while modern browsers get the full experience.

Here's an example, taken from this excellent article - modern browsers get the light bulb with the glow:

IE6 and under get the gracefully degraded experience and no glow:

Another pain point is that Photoshop doesn't produce semi-transparent PNG8 (although they came up with the name "png8" instead of saying palette or indexed PNG). Only Fireworks does export alpha transparent PNG8, which makes it a bit of a challenge. You also need a good designer to undertake this tricky task of making sure the image looks OK in both experiences. One way is to assume you're working with a GIF and then upgrade the experience with the carefully selected semi-transparent pixels. It could also help you keep the gif-like version in a layer and use other layers for the semi-transparent stuff, so you can quickly preview what the image will look like in IE6.

In any event - the important thing to remember is that in the worst case (IE6) PNG8 is as good as a GIF.

PNG doesn't work in browsers?

PNG works in browsers since forever with the exception of two edge cases:

  • the glitch where PNG8's semi-transparency is gone in IE6 (see above), but here GIF can't help you either
  • transparency in truecolor PNGs is shown as a solid (usually grey) color in IE6. But again - GIF can't help here either, because it doesn't support alpha (variable) transparency to begin with. People often use GIF to "solve" this problem (moving to GIF will mean potentially losing colors), but if you can solve with a GIF, you can solve it even better (and with smaller filesize) with PNG8

Another solution to the second problem is to use IE's AlphaImageLoader CSS filter (and there's a number of scripts to do so automatically), but this filter has serious performance drawbacks and should only be used as a last resort. Three things to try before resorting to AlphaImageLoader:

  1. Try PNG8 for progressively enhanced experience
  2. Try without transparency - if the background is a solid color, convert the image to use the solid color. In imagemagick you can use -flatten for this purpose:
    $ convert source.png -flatten -background yellow result.png
  3. Forget about IE6 :)

If you end up using AlphaImageLoader, make sure you use the underscore hack so that only IE6 users experience the performance degradation.

#some-element {
    background: url(image.png);
    _background: none;
    _filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='image.png', sizingMethod='crop');
}

PNGs are bigger than GIFs?

This misconception comes from the fact that people compare truecolor PNG with GIF which is not a fair comparison because you often compare image with thousands of colors (the PNG24) with an image with 256 colors (GIF). Often people work on an image in Photoshop or another program and when they decide to export for the web, they try PNG24, see that it's bigger and switch to GIF. But in this step GIF may strip a lot of colors. And if you're going to strip colors, well, PNG8 will give you the same colors and smaller filesize. (Another thing is that sometimes Photoshop does a poor job exporting the PNG8. If the PNG8 looks crappy, but the GIF is OK, then export as a GIF but then convert to PNG with another utility, such as optipng)

Again - PNG8 is the file format comparable to GIF and it's almost always smaller in filesize than GIF.

Comparing GIF vs. PNG filesizes

(This and the next experiment is something I did over an year ago, bored to death in the middle of the ocean on the board of a Carnival cruise ship, but since then I never really looked at the data. So here's my chance to flush some old data and clean up 20 Gigs of lets-keep-just-in-case test images :) )

Using Yahoo! image search web service I downloaded some GIFs (matching the queries "logo", "mail" and "graph"), ended up a little over 1700 images. Then I used optipng to convert them all to PNG and see the results.

I used OptiPNG simply with no special options:

$ optipng *.gif

As the next experiment will show, optipng can do better, so can pngout for example. So consider these results the least you can do to make GIFs smaller (by turning them to PNG)

So some stats from the experiment:

  • The average, median actually, GIF image on the web (last year, judging from this small sample) is 525x388 and has 139 colors (I just love semi-useless stats ;) )
  • The median GIF is 24K
  • After conversion to PNG, the median becomes 18K
  • The median savings from converting all GIFs to PNG is about 23%

Interestingly enough 4% of the images were smaller as GIFs - utter disappointment (and don't tell anyone!). So I had to try just a little harder. I didn't run OptiPNG with its best -o7 option, but ran PNGOut instead. The results is that now only 4 of the 1706 images were smaller as GIF. I'm pretty sure that trying a little harder (with PNGSlim, see yesterday's post) would've probably fixed it, but 4 out of 1700 is something I could live with. BTW, the images where OptiPNG failed to produce smaller PNG, then PNGOut converted with the ratio of 21% median savings. Not bad for taming the few shrew GIFs.

BTW, some GIFs lost over 100K of filesize, the max was over almost 600K savings! So, you never know.

If you like to look at numbers, here's a csv dump - the optipng results and the selected few that ran through PNGout.

So, take-home message: turn your GIFs into PNGs and win at minimum 20% fewer bytes over the network.

Comparing PNG optimizers

For this experiment I downloaded over 12000 images (again, Yahoo! search API) and ran them through a bunch of optimizers, sometimes with different options. In retrospect, it's probably not that useful of an experiment, because (see previous post) different optimizers specialize in different areas - compression, pre-compression filtering, chunks removal, etc, and your best bet is to run several tools. But still it's at least some data points (the cvs dump is here)

The images were 1000 matches for each of the searches for "baby", "background", "bkg", "flower", "graph", "graphic", "icon", "illustration", "kittens" (of course), "logo", "monkeys", "png", "transparency". After removing 4xxs, 5xxs and other mishaps and cleaning up a bit, I ended with over 10000 images. I ran them thorough:

  • pngcrush - pngcrush -rem alla -reduce before.png after.png
  • pngcrush-none - to keep all chunks pngcrush -rem none -reduce before.png after.png
  • pngcrush-brute - more filter attempts - pngcrush -rem alla -brute -reduce before.png after.png
  • pngout - pngout /q /y /force before.png after.png. default compression level in PNGOut is "extreme", so I tried two less extreme below
  • pngout-match - pngout /s2 /q /y /force before.png after.png
  • pngout-intense - pngout /s1 /q /y /force before.png after.png
  • pngrewrite - pngrewrite before.png after.png PNGRewrite only works with PNG8, it also converts truecolor to PNG8 whenever the truecolor happens to be under 256 colors,
  • optipng - optipng before.png -force -out after.png. OptiPNG's default level is 2 (of 7) so I had to try below and above the default:
  • optipng1 - optipng before.png -o1 -force -out after.png
  • optipng3 - optipng before.png -o3 -force -out after.png
  • optipng7 - optipng before.png -o7 -force -out after.png
  • advpng - cp before.png after.png; advpng -z -f -q after.png
  • advpng-insane - with the "insane" 4th level of compression cp before.png after.png; advpng -z4 -f -q after.png
  • deflopt - cp before.png after.png; deflopt -s -f after.png
  • pngoptimizercl -cp before.png after.png; pngoptimizercl -file:"after.png"

And the results:

Tool Median time to run Median savings Success rate
pngcrush 0.25s 6.06% 93.85%
pngcrush-none 0.23s 5.58% 90.22%
pngcrush-brute 3.08s 8.10% 96.31%
pngout 1.89s 12.21% 94.35%
pngout-match 0.22s 13.89% 44.57%
pngout-intense 1.63s 12.10% 94.22%
pngrewrite 0.07s 29.84% 22.37%
optipng 0.23s 7.32% 93.21%
optipng1 0.10s 4.24% 85.16%
optipng3 0.66s 7.10% 94.26%
optipng7 4.13s 7.57% 94.81%
advpng 0.34s 11.55% 52.47%
advpng-insane 0.76s 15.64% 56.09%
deflopt 0.34s 0.44% 96.94%
pngoptimizercl 0.48s 9.71% 97.99%

"Success rate" is how often the tool managed to produce a smaller result than the original. For example PNGRewrite's success rate is pretty low, because it only works with up to 256 colors. Median time to run is the median value that the tool takes to optimize one image.

And now, madames et monsieurs, introducing...

Give PNG a chance (.com)

I hope you'll find this as funny as I do, I thought it was pretty funny, at least in my head :)

My secret goal was that everybody who hears the song or watches the video, will think twice the next time when doing "Save for the Web..." in Photoshop.

Enjoy!

Music: Drums from GarageBand, I play two guitars, also bass (a guitar with effect actually) and vocals. If you think you hear a woman's voice, it's still me, with "Helium" effect. The MP3 is here. If you want to experiment with the song yourself, here's a zip with each channel as an MP3.

Video: It may be lousy, but it's all web dev :) It's all JavaScript and CSS. The video is a screen capture of the Safari window. Also there are no images, only HTML entities. Heavy use of -webkit-* animations and transitions. The source and a live version you can play in Safari is here. The StarWars-like effect is borrowed from here.

The http://givepngachance.com URL is currently pretty blank, but I intend to add more PNG-related stuff there. Oh, and the lyrics.

Thanks!

Thanks for reading. And watching. And listening. Peace. And give PNG a chance :)

 

Big list of image optimization tools

Saturday, December 12th, 2009

2010 update:
Lo, the Web Performance Advent Calendar hath moved

Dec 12 This post is part of the 2009 performance advent calendar experiment (12 articles down, 12 more to go). Stay tuned for the articles to come.

Let's continue the topic of reducing file sizes started with the previous post and talk about making images smaller.

Engineer's guide to smaller images

Just to set the frame of the discussion - this is not about using Photoshop or setting the quality of the JPEGs and so on. I realize that we, web developers, wear many hats - we're designers, client/server coders, Apache/Linux admins, database heros. But this post is not about using image programs and assumes that you or your designer has already created the images to be used on the site with the appropriate colors, quality and so on.

Now, repeat after me: you should never take an image from the designer and put it up on the web.

Most often this image is bigger than it should be. It's not the designer's fault, it's usually the software used to produce the image.

You shouldn't put an image up on your server before running it through a few tools. These tools are free, open source, cross-platform and can be run on the command line, hence scripted and run in batches over a large number of files - by you, or even better, automatically by a build deployment process. It's OK to batch-run those files without human intervention, because these tools simply optimize the files, they don't change the pixel information, so the "after" images look exactly like "before", only smaller.

Selecting the right file format

The first step towards leaner images is to select the correct file format. There are three options:

  1. JPEG for photos. Photos contain millions of colors and smooth transitions of colors. Blue skies, clouds, sunset, your dog, lolcats - all photos.
  2. GIF is for the occasional "loading..." progress animation. This is it, no other uses for GIFs.
  3. PNG is for everything that's not a photo or an animation. That includes all icons, graphs, buttons, gradients, and what not. Any image with sharp transitions of colors. Think (but don't use for) text. Sharp transitions become "dirty" in JPEG.

PNG is an interesting topic for a follow up post, for now let's stop here. If it's not a photo, it should be PNG. An edge case is a screenshot for example. Depends on what's on the screen of course, but most often JPEG will give a smaller size if you can live with the artifacts around the sharp edges (like text).

Optimizing GIFs

Unfortunately many people still use GIFs even for non-animated images. That's a mistake. PNG is a superior format and yields smaller file sizes.

People still use GIFs because they think either that a/ GIF is smaller than PNG or b/ there's lack of support for PNG in browsers. These are misconceptions and I'll talk more about them tomorrow.

So, the way to optimize a GIF is to convert it to PNG.

You can use many tools to turn your GIFs to PNGs, including ImageMagick and OptiPNG.

# option 1: ImageMagick (if you know the filename)
$ convert logo.gif logo.png

# option 2: ImageMagick again (if you just convert all files in a directory)
$ mogrify -format png *.gif

# option 3: OptiPNG
$ optipng *.gif

These are just some of the options, I'm sure there are others.

After the conversion you can optimize the new PNGs like all other PNGs

Optimizing PNGs

There are various ways to write a PNG file. Unfortunately not all image editing programs do a good job at writing PNGs for the minimal file size.

Luckily, to fill the void, there's a great number of tools that excel in writing small PNGs. There are different ways to optimize a PNG:

  1. Stripping out "chunks" - PNG is an extensible format. Extensions come in the form of chunks and most chunks are not needed for the web.
  2. Reducing the number of colors and switching between PNG types - truecolor PNG, grayscale, palette...
  3. Chosing the best "filter". Filters are a pre-compression step. You can compress any type of file, but when you know the file is an image, you can do better. Filters are for this purpose.
  4. Optimizing the actual DEFLATE compression algorithm

Different tools specialize in one or more of these areas. So the more tools you run, the better the results will be. But you have to run at least one tool, always. You'll be surprised how unoptimized are most PNGs coming from common commercial image programs.

So, to optimize a PNG you shoul run as many of the following programs as possible:

# optipng (skip -o7 to run faster)
$ optipng -o7 my.png

# pngcrush (skip -brute to run faster)
$ pngcrush -rem alla -brute -reduce my.png my.png.temp
$ mv my.png.temp my.png

# pngout - closed source, non-windows binaries here
# (add parameter -s2 to run faster)
$ pngout my.png

# advpng (use -z2 to run faster)
$ advpng -z4 my.png

# deflopt - windows only
$ deflopt my.png

Other tools to note include PNGrewrite, PNGNQ and PNGquant, but they are limited because they deal only with PNG8 (256 colors) files. PNGNQ and PNGQuant are actually converters from truecolor to PNG8, so they are not guaranteed to be lossless. PNGreqwrute is safe to use, it will just silently fail if the file has more than 256 colors, so there's nothing to lose.

Oh, and another, excellent tool - PNGOptimizer, windows-only has both command line interface and a GUI.

PNGSlim for the hardcore PNG optimization

If you're really serious about optimizing your PNGs, the tool is called PNGSlim. It's a Windows-only batch file that runs pretty much all tools above and runs them (especially PNGOut) with all kinds of parameters, hundreds of times. So it can take a while to run.

Optimizing JPEGs

JPEG is a lossy format (you lose information every time you save it, even if you choose 100% quality), but there are some operations that can be done losslessly - such as tweaking comments and meta information, cropping, rotating to 90, 180, 270 degrees. The tool that does this magic is called JPEGTran and is likely already on your unix/linux box. If not - here's how to install it (for Windows - get the .exe here)

So to optimize a JPEG losslessly you remove the meta information and optimize the so called Huffman tables. For bigger JPEGs (bigger than 10K) you can also convert the image to progressive coding.

# strip meta and optimize
$ jpegtran -copy none source.jpg > destination.jpg

# strip meta and convert to progressive coding
$ jpegtran -copy none -progressive source.jpg > destination.jpg

# keep all meta but still optimize
$ jpegtran -copy all source.jpg > destination.jpg

Important note on stripping meta

Only strip meta information from images you own the rights for and have permission. Otherwise you're committing a crime. Photographers put important copyright information in meta markers.

Optimizing GIF animations

Remember - no GIFs other than animations. For animations, run GIFsicle (pronounced "yo' mama" :) ) berfore you put them up:

# GIFSicle
$ gifsicle -O2 source.gif > destination.gif 

More tools?

These are the core tools for image optimization. There's a number of wrapper tools that are more or less UIs on top of these, because there are people who don't like consoles (really?!). I'll list the few that I can think of, please comment if you know of others, especially for windows. It's nice to give nice UIs to give to designers so they can drag-drop optimize images too.

  • smush.it, created by yours truly and Nicole Sullivan, now part of YSlow - runs pngcrush, jpegtran, gifsicle
  • PageSpeed runs optipng, jpegtran
  • PunyPNG - originally inspired by smush.it, but more advanced
  • ImageOptim - Nice easy UI for Mac, runs most of the tools above (hope your company firewall doesn't block the site because of the domain name ;) )
  • PNGSquash another UI for Mac, runs advpng, pngcrush, optipng
  • PNG Monster is for Windows, runs many PNG tools, you can drag/drop on it
  • IrfanView - my favorite image viewer for Windows has a plugin to use PNGOut
  • WP-Smushit is a WordPress plugin by Alex Dunae which sends all your image uploads to smush.it for optimization. Talk about easy!

More reading

Series of articles on YUIBlog:

presentations:

and more:

Thanks!

Thank you for reading. Now you have a whole lot of tools/toys to install and play with. Image optimization is an easy way to improve performance, it's just running a bunch of tools. You don't need to worry that the quality will suffer (so the designer won't be disappointed in you :) ). So you can only win. You may win quite a bit, you may win just a little (anywhere between 5 to 30% savings is what I've seen on random live sites when I was working on and testing smush.it). The thing is you'll almost always win something.

And because it's human to forget to optimize the images before you push them live, do take the time to setup the optimization step as part of the automated deployment process.

And to summarize once again the steps of what this automated process would be:

  1. Convert GIFs to PNG. Then relax and take a deep breath (instead of flaming the unfortunate soul who created the GIFs) while you string replace "gif" with "png" in all your styleshets
  2. Run PNGs through optipng, pngcrush, pngout, any or all of the tools listed above
  3. Run JPEGtran
  4. Run GIFsicle
 

Duplicates and near-duplicates

Wednesday, December 9th, 2009

2010 update:
Lo, the Web Performance Advent Calendar hath moved

Dec 9 This post is part of the 2009 performance advent calendar experiment. Stay tuned for the next articles.

One of Yahoo!'s first batch of performance best practices has always been "Avoid duplicate scripts" (check Steve Souders' post). Later we added "... and styles". This is a pretty obvious, kind of a "Duh!" type of recommendation, it's like saying "Avoid sleep() in your server-side scripts". But it didn't come up out of thin air, duplicates were noticed on some quite high-profile sites.

Duplicates are easy to spot (and YSlow will warn you), but let's talk a bit about another concept - let's call it near-duplicates - when two components are similar, almost the same, but not quite.

Duplicate scripts and styles

As a refresher and a quick illustration of the effects of duplicate scripts and styles, fire off your HTTP sniffer and hit this test page.

(btw, this is a simple page I put up to test different YSlow scenarios, you can actually use it as a web service of sorts to create any type of components with different options)

Firefox 2 downloading both duplicate styles and scripts:

FF2 duplicate scripts and styles

IE6 and duplicate scripts:

IE duplicate scripts

Exact details of when/which browsers chose to download duplicates are not that interesting, it's obviously bad to waste time downloading the same resource. Even if no repeating download happens, the browser still has to parse through and execute the script/style for a second time.

Even if you have iframes you don't need to repeat the same JS/CSS in each frame, you can "borrow" them from the parent page, here's an example.

Near-duplicates

Near duplicates can be:

  • components with the exact same response bodies but different URLs causing the browser to do double work
  • components (images) that are too close to each other - in terms of looks or purpose. Only one component should be selected in this case.

Same component, different URLs

This could happen especially when you have user-generated content such as image uploads for profile photos and avatars in social sites, forums, images people put in comments on MySpace and so on.

Also images of stuff for sale (Craigslist, eBay). Often different sellers offering the same item would take the same photo from the manufacturer's site and upload it over and over again.

Luckily, PageSpeed warns about components with identical content, so those can be identified:

In the screenshot above, you see one image (2.3K) repeated 3 times, another (the iPhone, 1.7K) is repeated 4 times, and yet another one (2.8K) repeated 2 times.

It's not exactly trivial to avoid this type of duplications with user-generated content (for example, the first poster may delete the photo, in which case the second poster's photo will need to "shine through"). But it's not impossible, using for example a hash of the component's content as an identifier.

Loading...

Ajax loading indicators are a great idea to give feedback to the user that something is happening. They come in all shapes and sizes... sometimes on the same page, unfortunately. And again, sometimes it's the same stock image but used at different stages of gradual "ajaxification" of the page and with different URLs.

As we're moving more and more towards modular pages and client-side logic, often different modules on the same page are coded by different teams at different times, independently, without being aware of each other's assets. This way of building pages has it's challenges and one is that common components, such as Ajax loading indicators, should be shared.

Too similar modules

Along the same lines - different modules are sometimes created by different designers at different times. The result - one rounded corner box with 1px shadow and one with 2px shadow, both on the same page. Or two different shades of the same gray color, which no one can tell apart. That's just a waste. (See Nicole Sullivan's presentation for illustration, e.g. slides 44, 45)

Below is an example, can you tell that these 5 rounded corner boxes are all different - slightly different shadow, color or radius? How many different boxes does this page need?

Different sizes of the same image

It's highly recommended to not scale images in HTML (or CSS). If you need a 100x100 image you don't use a 400x400 one with <img width="100" height="100" ... />. That's a good rule of thumb... to break sometimes ;)

In cases where the same image is used with different sizes and likely even on the same page, it may be beneficial to reuse the same bigger image and scale it down, because this could be saving extra HTTP requests of downloading the same (but slightly smaller) image.

Facebook is an example, the same hairy guy on the screenshot has two images with different sizes. It's actually the same image but resized in CSS.

The relevant CSS which shows the profile image in LARGE and SMALL (and looks like there's even a TINY view, although I couldn't find an example on this page)

.UIProfileImage_LARGE{width:50px;height:50px}
.UIProfileImage_SMALL{width:32px;height:32px}
.UIProfileImage_TINY{width:25px;height:25px}

Thank you!

Thanks for reading! Reducing HTTP requests is critical for page performance. You've merged your scripts and styles as much as reasonable, you've crafted CSS sprites and inlined images with data URIs. Now it's time to look at what's left - are there components that are way too similar, are there any near-duplicates or even exact duplicates? Same image on different backgrounds? Ever-so-subtle gradients and shadows? Time to pick up the old axe and cut.

 

Data URIs, MHTML and IE7/Win7/Vista blues

Monday, December 7th, 2009

2010 update:
Lo, the Web Performance Advent Calendar hath moved

Dec 7 This is the seventh in the series of performance articles as part of my 2009 performance advent calendar experiment. Stay tuned for the next articles.

UPDATE: While this post is an interesting study, the problem it solves turns out to be much simpler. The details are here. In resume: you need a closing separator and it all works fine in IE7/Vista/Win7

Let's start this post as a dialog:

- Data URIs are a way to embed the base64-encoded content of images inside HTML and CSS. Inlining images in markup or stylesheets helps you save precious HTTP requests. It's an alternative to CSS sprites.
- BUT! IE6 and IE7 don't support data URIs
- Yes, for them you can inline the images using MHTML
- BUT! Looks like IE7 on Vista and IE7 on Win7 have problems with MHTML
- [Sigh...] For those browser/OS combos there's a solution too.

I'll quickly go over what are data URIs and how MHTML addresses IE6 and IE7. If you're familiar with these, feel free to skip down to the Vista blues part.

Data URIs

Here's how to use data URIs in your pages:

  1. take an image:
    horoscopes icon image
  2. Use PHP to read this image's binary content and encode it using base64 encoding:
    $ php -r "echo base64_encode(file_get_contents('horoscopes.png'));"
    iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAADAFBMVEX///8mPnru9...
    ...more scary stuff goes here...
    dajNGGzlDAAAAABJRU5ErkJggg==
  3. To use this image in CSS, paste the encoded image content in the stylesheet following this syntax:
    .horoscopes {
        background-image: url("data:image/png;base64,iVBOR...rkJggg==");
    }

    Note: the trailing == is part of the image content it's not part of the syntax

  4. Alternatively if you want to use this image in the HTML, you can go like:
    <img src="data:image/png;base64,iVBOR...rkJggg==" />

And that's about it for data URIs, it's quite simple actually.

Data URIs in the wild

To see real-life, high-traffic sites using data URIs look no further than your favorite search engines.

Yahoo! Search using data URI in CSS for a button background:

yahoo search screenshot

Google Search using data URI in an IMG tag for a video thumb:

Google search screenshot

Base64-encoded filesizes

The base64 encoding adds about 33% to the filesize. But, then you gzip the result, the compression brings the size back to the original. Plus or minus. Interestingly enough, sometimes after base64 and gzip, the result is smaller than the original. But don't count on that.

Theoretically also when you base64 encode a bunch of images and inline them in the same CSS or HTML, you'll have a larger string to compress, so increasing the chance of repetitions and compressing better. (Todo: test this assumption)

In any event, the benefit of reducing HTTP requests will likely greatly outweigh any fluctuation in the file size.

MHTML

IE8 supports data URIs, but earlier IEs do not. For them you can use MHTML (Multipart HTML, blogged previously here)

Continuing with the previous example, in order to display the horoscopes image in IE < 8 you can use a stylesheet like this:

/*
Content-Type: multipart/related; boundary="_MY_BOUNDARY_SEPARATOR"
 
--_MY_BOUNDARY_SEPARATOR
Content-Location:horoscopes
Content-Transfer-Encoding:base64
 
iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAADAFBMVEX///8mPnru9....U5ErkJggg==
*/
 
.horoscopes{*background-image:url(mhtml:http://example.org/styles.css!horoscopes);}

A few notes on this example:

  • the image content is base64-encoded just like before
  • if you need more images, use your chosen separator prepended with --, in this case --_MY_BOUNDARY_SEPARATOR
  • Content-Location:horoscopes is how you give a name to the image in order to use it later
  • then later in your stylesheet you can reference the name in the stylesheet using http://url/styles.css!horoscopes
  • you need absolute URLs in the mhtml:http://.... part (that's retarded, hope I'm mistaken. Didn't work for me with relative URLs)
  • you can use a single CSS file to support both old IE as well as new browsers, but you'll have to repeat the image stream twice, probably a better approach is to use browser specific stylesheets

For more examples of MHTML (which is also used for multipart email messages) check this and for a brilliant example of using one MHTML to embed images in HTML (not CSS), check Hedger's test page here (appears 404 at the moment of writing). For a tool to automate the dataURI-zation/MHTML-ization, be sure to check Nicholas Zakas' CSSEmbed

The problem with IE7 on Vista (and Win7)

So far we have data URIs and MHTML fallback. Turns out we're not done yet, because IE7 on Vista and Windows 7 fails with the MHTML example. Two points:

  • IE8 is the default in Windows 7, but it comes with several IE7 modes (either by default or turned on by users when their favorite pages break). Compatibility view, ie7 mode, ie8 in ie7 mode, blah-blah, it's all pretty confusing, but all modes with the exception of IE8 in proper IE8 standards don't work with the MHTML
  • I tested Windows 7 evaluation copy, but I have the bad, bad feeling that Windows 7 normal copy will be as broken when it comes to MHTML. I believe it has something to do with security, if anyone finds an article on MSDN, or anywhere, please share.

The solution to the Vista problem is to split the CSS shown above (the one that uses MHTML) in two: one which contains the encoded images (the commented part) and a second one which only has the normal CSS selectors part.

In other words have something like:

  1. mhtml.txt:
    Content-Type: multipart/related; boundary="_MY_BOUNDARY_SEPARATOR"
    
    --_MY_BOUNDARY_SEPARATOR
    Content-Location:horoscopes
    Content-Transfer-Encoding:base64
    
    iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAADAFBMVEX///8mPnru9....U5ErkJggg==
    

    No need to use comments. Could be .txt, .css or anything you like. In my tests the content-type didn't matter - text/html, text/css, text/plain all worked

  2. styles.css:
    .horoscopes{*background-image:url(mhtml:http://example.org/mhtml.txt!horoscopes);}

    The normal CSS simply references the new MHTML file appending !identifier

  3. in your HTML you don't need to reference the MHTML document, you just point your link tag to styles.css as usual and styles.css contains the reference to the MHTML doc

Time to celebrate? Almost.

Another ugly bug surfaces - this procedure outlined above works only once. Once the MHTML.txt is cached, it stops working. That means repeating visits to the page or other pages using the same mhtml. The effect also means hovering on the same page. Say you have two images - one normal and one mouse over, both in the same MHTML. The normal works and the hover breaks, mouseout breaks too. Insane, isn't it?

There's a solution though - you should make the browser request the MHTML document every time. This is kind of backwards when it comes to performance optimization, where we want to cache everything. It's pretty unfortunate. The best you can do is avoid sending the MHTML every time, but only send a "not-modified" header. In order for this to work, the browser has to send If-Modified-Since or If-None-Match.

So you can send your MHTML file with an ETag (yes, ETags can help sometimes ;) ) Then the browser will send an If-None-Match and you can happily reply "304 Not Modified".

Sounds complicated? It sure is. And, remember, all this is only for IE7 (or any IE7 mode) on Vista and Windows 7. All other IEs on all other platforms work with the easy MHTML and IE8 works with data URIs.

Everything is a trade-off (see last night's post) and I can understand how you may think "That is one big tradeoff". There's always the option of serving normal images to the affected browser/os and just don't implement this optimization for them.

But it's good to know there is a solution.

DataSprites class

Let me offer this DataSprites PHP class I coded that takes care of all the scenarios. It takes a bunch of image filenames as input and produces:

  • a CSS containing data URIs for normal browsers (example output)
  • an MTHML CSS fallback for IE6,7 (example output)
  • a CSS that refers to an additional MHTML for IE7 on Vista, Win7 (example output - css and mhtml). The MHTML in this case is sent out with an ETag (derived from the input image filenames) and consecutive requests with the same ETag return 304 Not Modified

You can see a test page here and view the source code here. The directory listing is also available if you want to grab the images for testing.

The perfect use case for such an approach is when you want to combine background images on the fly. When you would normally use CSS sprites, but you want to produce them dynamically at run time (maybe because you have too many combinations?).

I've tested this in Safari, Firefox, Opera, IE6, IE7 and IE8 (in all compat modes) on Vista and Windows 7 evaluation copy. If you find the test page is not working for you, please let me know.

In this DataSprites class, I'm checking the problem OS looking for the strings "Windows NT 6" and "Windows NT 7" in the user agent string. My Win7 evaluation copy had "Windows NT 6.1" in the UA, so I may be a little forwards-aggressive with the check, but somehow I thought Win7 proper should have "Windows NT 7" in the UA string. And also that Win7 will suffer from the same issue as Vista and Windows 7 evaluation.

UPDATE: Added a hover example.

UPDATE 2: Recorded a screencast to demo the IE7/Vista experience

Thanks!

Thanks for reading! Now you know all about data URIs, MHTML, and maybe a little too much about IE7/Vista's challenges :) Ready to use data URIs and save some HTTP requests?

 

Reducing the number of page components

Saturday, December 5th, 2009

2010 update:
Lo, the Web Performance Advent Calendar hath moved

Dec 5 This is the fifth in the series of performance articles as part of my 2009 performance advent calendar experiment. Stay tuned for the next articles.

Let's talk a but about waterfall optimization - the first thing that happens in Mr.Page's life. The best way to optimize and speed up the waterfall is to have less stuff in it. The fewer page components, the faster the page - simple as that.

Fewer components vs. component weight

The size of the page components, meaning their size in kB, is important. It makes sense - smaller pages will load faster, 100K JavaScript will load faster than 150K. It's important to keep sizes low, but it should be clear that the number of components is even more important than their filesize.

Why? Because every HTTP request has overhead.

OK, but how bad can it be, someone might ask. If you look at an HTTP request - it has a header and a body. A 100K body will greatly outweigh the size of the headers, no matter how bloated they are.

Here's the headers for a request to Yahoo! Search:

Host: search.yahoo.com
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5;) Firefox/3.5.5
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive

To that request the server responds with the body (content) of the response prepended with some headers like:

HTTP/1.1 200 OK
Date: Sat, 05 Dec 2009 07:36:25 GMT
P3P: policyref="http://p3p.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR... blah, blah"
Set-Cookie: sSN=nTMt3Lo2...crazy stuff...nLvwVxUU; path=/;domain=.search.yahoo.com
Cache-Control: private
Connection: close
Content-Type: text/html; charset=ISO-8859-1

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" 
"http://www.w3.org/TR/html4/strict.dtd">

<html lang="en"><head><meta... 

This is 352 bytes request header and 495 bytes response header. Not so bad, eh? No matter how hard you try to make your cookies monster-size, the response body (9k gzipped in this case) will always be significantly bigger. So what's the problem with the overhead of the HTTP requests then?

The size of the headers is a problem when you make requests for small components - say requests for small icons - for example 1K or under. In this case you exchange 1K headers to get 1K of useful data to present to the user. Clearly a waste. Additionally this 1K of headers can grow once you start writing more cookies. It may very well happen that the HTTP headers size is bigger than the actual icon you need. And even if the headers are not bigger that the component, they are still big when you think percent-wise. 1K of 10K is 10%.

But the HTTP headers size is only one (and the smaller) of the problems.

The bigger problem is the HTTP connection overhead.

HTTP connection overhead

What happens (at a high level) when you type a URL and hit Enter? The browser sends a request to the server. Which server? The browser needs to know the IP address of the server, so if it doesn't have it in the cache, it makes a DNS lookup. Than the browser establishes a connection to the server. Then it waits for the first byte of the response from the server. Then it receives the full response (payload).

Here's how this looks like graphically represented by webpagetest.org

And the color legend:

  1. DNS lookup
  2. Initial connection
  3. TTFB (Time to first byte)
  4. Payload

So what do we have here - looks like in this particular case the browser is downloading content about 40% of the time. The rest of the time it's... well, not downloading content. How's that for an overhead. And the part of not downloading can be even greater, this above was just one example.

Now how about this - a bird-eye view of the 4 last pages in webpagetest.org's test history - just some random pages people have tested.

Do you see a lot of blue (time spent downloading content). Not as much as you would've hoped. There's some DNS lookups, some orange... and OMG, talk about going green! :)

In fact you may notice that the smaller the component, the smaller is the blue part.

What does all this tell us?

  1. A significant chunk of time is spent in activities other than downloading.
  2. Smaller components still incur HTTP overhead and for them the relative penalty (relative to their size) is atrocious.

So what's a performance optimizer to do? Reduce the number of components and so pay fewer penalties.

Just remove stuff

Truth is - a lot of stuff on the pages today is not needed. Features no one likes or uses clutter the page and make it heavier. Well, what can you do, the boss/client/marketing guy wants that feature there. What you can do is at least try. You can introduce some science into the marketing activities - measure how much a specific feature is used. Or if you already have the data - look at it. Decide what can a page go without.

It's going to be tough convincing people to remove stuff. After all you spend time developing it. Someone dreamt up that feature initially. Someone (not the users) loves it. People hate to let go. But still, it's worth to try.

Combine components

Now that the phase of convincing people to remove stuff is done, what's left needs to be combined. How do you combine components? Simple - all JavaScripts go into a single file, all CSS into a single file. All decoration images go into a sprite.

JavaScript example (from a page to remain anonymous)

Before:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script>
<script src="/javascripts/application.js?1258423604"></script>
<script src="/javascripts/ui/minified/jquery.ui.all.min.js?1258423604"></script>
<script src="/javascripts/ui-ext/ui.bgiframe.min.js?1258423604"></script>
<script src="/javascripts/ui-ext/ui.stars.pack.js?1258423604"></script>
<script src="/javascripts/ui-ext/ui.dimensions.js?1258423604"></script>
<script src="/javascripts/ext/jquery.form.min.js?1258423604"></script>

After:

<script src="/javascripts/all.js"></script>

Sizes, gzipped: 70029 bytes before, 65194 bytes after. Simply merging files and there's even 6.9% saving!
And the more important saving: 6 less HTTP requests

Repeat for CSS. Before:

/stylesheets/general.css?1258423604
/stylesheets/global.css
/stylesheets/ui.stars.css
/stylesheets/themes/enation/enation.all.css
/public/template/css/132/1245869225

After:

<link type="text/css" rel="stylesheet" href="/stylesheets/all.css" />

Sizes, gzipped: before 14781 bytes, after 13352 bytes, saving 9.6%.
But the bigger saving: 4 less HTTP requests.

If you wonder how come the sizes before and after are different since we merely concatenate the contents of the files, well, the savings come from gzip compression. When you have more characters in the file, there's more chance that some will repeat which means they will compress better. That's one. And then, the compression itself has an overhead which you incur once for the whole bundle of files, as opposed to for every file.

Now - let's make decoration images into sprites. Before:

... 15 image requests, 6.8K

After: (1 sprited image)

Result size: 1.4K, 7 times smaller!

Here the savings are so dramatic partially because the source files are GIFs and the result is a PNG8 but that's a whole other post.

So in conclusion: file concatenation is just awesome. You save both: bytes to download and, much more importantly, HTTP requests. Less of the green stuff in the waterfall!

x-type component concatenation

So far we combined .js with .js, css with css and images with images. How about a cross-component type concatenation?

You can inline images inside HTML and CSS (and why not JS if you want) using data URIs (another post coming).

And you can inline CSS and JS inside HTML too.

This means you can have your whole application inside of just one HTML file if you wish. Inside of the HTML you have inline styles, scripts and images.

Combining CSS with JS

Now how about mixing CSS and JS into one component. You can do that, and it's especially suitable for lazy-loaded, widget-type functionality.

Say you've loaded the page, then the user clicks some rarely used button. You haven't downloaded that content that is supposed to wow the user upon click of the button. So you send a request to grab it. The new content may come in the form of a JSON string. And what if the new content requires some stylesheet that was not part of the base page? You'll have to make another request to download that stylesheet too.

Or, you can download both content and styles in the same JSON response. You simply inline the style information as a string in the JSON. So:

1. uses clicks, you request feature.js which goes like:

{"content":"<p class=\"wow\">I'm a feature</p>", "style": "wow{font-size: 60px}"}

2. You process the JSON and shove the content into the page

var o = JSON.parse(xhr.responseText);
$('result').innerHTML = o.content;

3. You add the styles to the head:

var wow = document.createElement('style');
wow.type = "text/css";
if (wow.textContent) { // FF, Safari
    wow.textContent = o.style;
} else {
    wow.styleSheet.cssText = o.style; // FF, IE
}
document.documentElement.firstChild.appendChild(wow);

Nice and simple. Makes the features (that progressively enhance the page) atomic and self-contained.

More to reducing components?

For more and creative ways to reduce HTTP components you can take a look at MXHR and Comet

Another thing to check is the Keep-Alive setting on your server. Remember how there were 4 steps in the component download. When you request a second component you can have the connection open so that you don't need to re-establish it (skipping step 2). And since the DNS lookup was already made you get rid of step 1. Skipping 2 out of 4 is not bad at all.

Summary

Reducing the number of page of components is the top priority of any web performance optimization effort. HTTP requests are costly. In part because of the headers size overhead, but mostly because of the connection overhead. The browser spends a disturbing amount of time not downloading stuff, and we can't allow this!

 

Performance Advent Calendar 2009

Monday, November 30th, 2009

2010 update:
Lo, the Web Performance Advent Calendar hath moved

I like 24ways.org's idea of a webdev advent calendar - publishing one article per day from Dec 1st to Dec 24. I thought it would be cool to have the same thing on web performance topics. Our young performance community would benefit from a year-end heartwarming bunch of articles, maybe some motivational, some wrapping up interesting ideas and techniques that popped up in the past year, this kind of stuff. Ideally it would be a co-effort by performance folks from around the world, but lacking the PM skills and time to organize it (and coming up with the idea kinda late) I'll try to start alone, hopefully next year it would be a community effort.

So this is now just a placeholder post, I'll update it with links to the articles as I write them, one per day is the plan, will see how it will work. So watch this space, you can follow me via the RSS feed or Twitter.

I have a preliminary idea of the articles I intend to write, but it's not at all final or complete, so if you want to see an article on something, or you want to write a guest post, please comment or get in touch in email.

  1. The performance roadmap
  2. Performance tools
  3. Required reading
  4. Psychology of performance
  5. Reducing the number of page components
  6. The pain points of having fewer components
  7. Data URIs, MHTML and IE7/Win7/Vista blues
  8. Collecting web data with a faster, free server by Christian Heilmann
  9. Duplicates and near-duplicates
  10. Caching vs. inlining
  11. Reducing the payload: compression, minification, 204s
  12. Big list of image optimization tools
  13. Give PNG a chance (video)
  14. Free-falling waterfalls
  15. JavaScript loading strategies by Ara Pehlivanian
  16. How To Measure Web Site Performance by Eric Goldsmith
  17. Rendering: repaint, reflow/relayout, restyle
  18. DOM access optimization
  19. The new game show: "Will it reflow?"
  20. Extreme JavaScript optimization by Ara Pehlivanian
  21. Progressive rendering via multiple flushes
  22. iPhone caching
  23. CSS performance: UI with fewer images
  24. The performance business pitch
 

Help write the lyrics to “Give PNG a chance”

Thursday, September 3rd, 2009

As you know yours truly is a guitar hero wannabe. So I'm playing with the idea of recording a song/video called "Give PiNG a Chance", cover of "Give peace a chance". Hopefully whoever hears it will then think twice before saving a GIF instead of PNG. Imagine. And the web will be as one... :D

Here's the lyrics so far, it's a work-in-progress. If anyone can think of rhyming Web 2.0 buzzwords, please comment.

Give PNG a Chance

#1
Everybody's talking about

Web 2
YouTube
WordPress
CMS
    Highrise
    Enterprise
    Crowd sourcing
    Open sourcing
Phone texting
Unit testing 
Foo bars
Avatars

All we are saying is: give PNG a chance

#2
Everybody's talking 'bout

Long Tail
Junk Mail
Fail Whale
Ruby Rail
    Webcasting
    Screencasting
    Podcasting
    Vodcasting
Cloud computing
Tele commuting
Trackbacks
Pingbacks
    Less is More
    Apple store
    Link whore
    Browser war

All we are saying is: give PNG a chance


#3
Everybody's talking about
RSS, CSS
HTML, XML
    SOA, RIA
    API, CGI
REST-POST, GET-HEAD
SOAP? Nope!
    SEO, SMO
    CPM, CRM
SQL, YQL
SMS, MMS

All we are saying is: give PNG a chance
All we are saying is: give PNG a chance
All we are saying is: give PNG a chance
All we are saying is: give PNG a chance
Yup, no more GIFs
All we are saying is: give PNG a chance
All we are saying is: give PNG a chance...

Unused buzzwords

Here's my laundry list of buzzwords I thought of but didn't use so far in the lyrics. If you think of good rhymes as you look through the list, please comment.

  • flickr
  • blog
  • blogging
  • wiki
  • twitter
  • facebook
  • outlook
  • ajax
  • comet
  • firefox
  • chrome
  • ie6.. ie8
  • explorer
  • navigator
  • digg
  • blogger
  • safari
  • opera
  • RoR
  • PHP
  • CDN
  • SMS, MMS
  • WTF
  • FTW
  • web service
  • Perl, cURL
  • Ruby
  • Python
  • social
  • social media
  • social tagging
  • click-through
  • agile
  • photoshop
  • rounded corners
  • fluid layout
  • iPhone
  • Air, PEAR
  • Sliverlight,
  • mobile
  • JavaScript
  • flash
  • buzzwords
  • keywords
  • microblogging
  • microformats
  • micropayment
  • semantic web
  • validators
  • web standards
  • folksonomy
  • taxonomy
  • tagging
  • phishing scam
  • spam
  • widget
  • gadget
  • geotagging
  • linkbait
  • bookmarking
  • viral
  • tag clouds
  • view source
  • torrents
  • bit torrent
  • github
  • blog
  • mashup
  • pagerank
  • social network
 

Ignite Velocity: Image Weight Loss Clinic

Wednesday, September 2nd, 2009

A few months ago I gave a 5 minute Ignite talk at the Velocity conference. (My previous post talks about the Ignite experience.) I thought they recorded the video and wanted to share but seems like it's not happening. So below are the slides from slideshare.

The ignite talk rules are: 20 slides that change automatically after 15 seconds. But I took the liberty of hacking the format a little bit. For one, the 18th (and 19th) slide which was my main point is duplicated. And then I had some transitions, like in the first two slides for example, where different text or pictures change after a timeout. I expanded those transitions for slideshare, so the slide count is 40-something, but at the presentation it was still total of 20 slides in 5 minutes.

If you follow the slides on slideshare, be sure to click the Notes tab which has what I said, more or less, at each slide. The full script is below.

And yes, if you like it, please vote for my South by Southwest panel proposal. It has the same name "Image Weight Loss Clinic" but will be a much longer version of this 5 minute Iginite and what is more - Derek Tonn of graphicsoptimization.com will share the stage with me. Pretty exciting, so cast you votes, leave a comment and help me get to SxSW.

The Script

Here's what I said, more or less:

  1. Let’s talk a little bit about cutting back on that bandwidth bill. Saving some money. And without touching the code. How do we do that? One way is…
  2. Images. Ever since the dawn of times people have been using images. Cavemen scraping on their cave walls… and today… Today we have websites. And LOLcats.
  3. Half of the average web page weight today is images. And most of these images are too fat. They could use a diet and lose some weight but without losing quality, not even a single pixel.
  4. So, welcome to the Image Weight Loss Clinic. Our first patient today is... some random page found on the Internets. Hmm, what have we got here… Header graphic, speaker photos, logos, rotating banners – the usual stuff.
  5. First problem - there are some GIFs. We shouldn’t be using GIFs. PNG is the format for graphics - buttons, icons, and so on. The palette type of PNG, also known as PNG8 is good as a GIF and actually better. And PNG filesizes are smaller. Well, except for really tiny GIFs, but let’s ignore these, they don’t matter much.
  6. You should convert all your GIFs to PNG8. There’s a number of tools to do this in a batch. You can use ImageMagick for example. “Instant results or your money back”. Especially since ImageMagick is free.
  7. For your JPEG photos, use JPEGTran. It’s free, cross-platform, command-line tool, easy to script. And it’s lossless, you keep the photo quality. If you run it without setting any options, that’s good enough. Run with the –optimize and you’ll get even better results.
  8. Option –progressive makes JPEGs that render in the browser from low resolution to high, as opposed to top-to-bottom. If the JPEG is bigger than 10K (which is most JPEGs), the progressive encoding has a good shot at giving you a smaller image. You can always brute force – run with both options and take the smaller result.
  9. Option –copy none strips all comments and meta data - camera model, geo location, everything. If you own the image, strip the meta data. You’ll be surprised how much bloat is in there. If you don’t own the image, check with the owner. Option –copy all keeps all meta data.
  10. Next – PNG. There’s a lot of options to optimize PNGs - all lossless and scriptable on the command line. Some even have nice graphical UIs built on top of them, you know, for our command-line-challenged friends.
  11. There’s even browser-based UIs, built on top of some of the tools. For example, smush.it, which is now part of YSlow runs PNGcrush. Google’s Page Speed runs OptiPNG.
  12. With so many PNG tools, how do you decide which one to pick? They are all good tools and they do their best, so if you’re in a hurry, just pick one and go with it, script it. You’ll be pleasantly surprised by the outcome.
  13. Now if you’re serious about PNG weight loss, you can run all the tools you can lay your hands on. Run the next tool on the result of the previous, try with their different options switches. They all do different things, and results may vary from one image to the next. Run them all in sequence and get an even smaller file at the end.
  14. Or, if you’re hardcore – run PNGSlim. PNGSlim is actually a Windows batch file that runs most of the other tools. It’s very slow, it can take hours to go through a handful of files. But at the end, it will give you the leanest, most optimized PiNG.
  15. Let’s see some results. That page has a total page weight of over 800K, including scripts, styles, everything. Over 80% of the weight is images. And what happens after optimization?
  16. We can strip quite a bit, over 200K of unnecessary image information. 200K, that’s over 30% if the image weight. The page looks exactly the same, pixel by pixel. There was no manual work involved, just running some tools. I think 30% is pretty impressive.
  17. To summarize, don’t use GIFs, run JPEGtran on your photos, and for the graphics - any PNG or all PNG tools.
  18. So the tools are available out there, now all you need is a process that runs the tools before your images go live. Make image optimization part of the push process.
  19. These are all lossless operations that don’t require you to look at the images to verify they’re OK. So you can only win! And often you’ll see some amazing and surprising results. Even if you normally do a good job with image sizes, there’s always a few that slip as you race against the deadline. The thing is – setup a process, so you don’t even need to think about it.
  20. Thanks very much, everybody! I’m Stoyan from Yahoo! Search. Enjoy Velocity, it's a great conference!
 

The art and craft of postload preloads

Thursday, August 27th, 2009

What can your tired old page, once loaded and used and read, can do for your user? It can preload components needed by the next page, so when the users visit the next page, they have the new scripts, styles and images already in the cache. Next page loads faster and the user's happy. On its death bed you tired old page has left a good inheritance for future generations. Good old page.

How can you go about preloading for the next page? By waiting for onload of the current page and requesting the new components. Here are 4 ways to do so, all using a timeout of 1 second after page load so that prefetching doesn't interfere with the user experience on the page.

One way... (DOM)

Using DOM you can create a new LINK element and a new SCRIPT element and append them to the HEAD. For images - it's a one-liner new Image.src="..."

The drawback of this method is that your CSS is executed against the current page and might affect display. Same for JavaScript - it's executed. The image is simply requested and never displayed.

window.onload = function() {
    setTimeout(function(){
    
        // reference to <head>
        var head = document.getElementsByTagName('head')[0];
    
        // a new CSS
        var css = document.createElement('link');
        css.type = "text/css";
        css.rel = "stylesheet";
        css.href = "new.css";
    
        // a new JS
        var js = document.createElement("script");
        js.type = "text/javascript";
        js.src = "new.js";
    
        // preload JS and CSS
        head.appendChild(css);
        head.appendChild(js);
    
        // preload image
        new Image().src = "new.png";
    
    }, 1000);
};

DOM test page

For this way of doing it you can use any JavaScript library's helper methods to load stuff on demand. Good examples - YUI Get and LazyLoad

... or another ... (using iframe)

Another option is to create an iframe and append your components to its head. Using an iframe you can avoid the CSS potentially affecting the current page. JavaScript will still be executed.

window.onload = function() {
    setTimeout(function(){
        
        // create new iframe
        var iframe = document.createElement('iframe');
        iframe.setAttribute("width", "0");
        iframe.setAttribute("height", "0");
        iframe.setAttribute("frameborder", "0");
        iframe.setAttribute("name", "preload");
        iframe.id = "preload";
        iframe.src = "about:blank";
        document.body.appendChild(iframe);
 
        // gymnastics to get reference to the iframe document
        iframe = document.all ? document.all.preload.contentWindow : window.frames.preload;
        var doc = iframe.document;
        doc.open(); doc.writeln("<html><body></body></html>"); doc.close(); 
 
        // create CSS
        var css = doc.createElement('link');
        css.type = "text/css";
        css.rel = "stylesheet";
        css.href = "new.css";
 
        // create JS
        var js = doc.createElement("script");
        js.type = "text/javascript";
        js.src = "new.js";
 
        // preload CSS and JS
        doc.body.appendChild(css);
        doc.body.appendChild(js);
        
        // preload IMG
        new Image().src = "new.png";
        
    }, 1000);
};

IFRAME test page

... I'm gonna find ya ... (static page in iframe)

If your components are static you can create a page that has them all and load that page into the dynamic iframe. Static means knowing them in advance, not relying on page's JavaScript to figure them out on the fly. As you can see, much simpler than the previous code.

window.onload = function() {
    setTimeout(function(){
        
        // create a new frame and point to the URL of the static
        // page that has all components to preload
        var iframe = document.createElement('iframe');
        iframe.setAttribute("width", "0");
        iframe.setAttribute("height", "0");
        iframe.setAttribute("frameborder", "0");
        iframe.src = "preloader.html";
        document.body.appendChild(iframe);
        
    }, 1000);
};

IFRAME static page test

... I'm gonna GETcha, GETcha, GETcha! (with Ajax)

Finally - Ajax. Scripts are not executed, CSS not used for rendering. Nice and clean. For simplicty the code has no support for browsers that don't know what XMLHttpRequest is.

window.onload = function() {
    setTimeout(function(){
        
        // XHR to request a JS and a CSS
        var xhr = new XMLHttpRequest();
        xhr.open('GET', 'new.js');
        xhr.send('');
        xhr = new XMLHttpRequest();
        xhr.open('GET', 'new.css');
        xhr.send('');
        
        // preload image
        new Image().src = "new.png";
        
    }, 1000);
};

Ajax test page

Thanks!

Any other ways you can think of?