Trimming the Fat

When I unveiled a new version of this site last year, I hoped the design would slowly evolve.

An update in February improved the responsive layout and saw some initial performance optimisations. The last few weeks have seen further iteration. Although the design looks remarkably similar, much has changed below the surface. Where each page previously requested at least 14 assets weighing a total of 385kB, now only 9 requests are needed, and with an unprimed cache, these total just over 100kB. I thought it would be interesting to detail the changes I’ve made, and this time, I’ve got graphs!

JavaScript

Uncomfortable with having 30kB of jQuery as a dependancy, JavaScript was my first target for weight loss. In reviewing the jQuery functions I was using, I realised many were unnecessary:

  • The Awesomersands function that allowed me to style ampersands was actually replacing the original glyph with a much uglier version. It also produced a distracting ‘flash of unstyled ampersand’.

  • A function that added thin spaces around emdashes could instead be incorporated into my Movable Type templates. In making this change, I decided to now use spaced endashes instead.

  • The HTML5 history.pushState function used on journal entry pages was fragile at best, so became a candidate for removal.

  • A function that wrapped a <div> around video embeds to give them a fluid width was unnecessary when I could add this manually.

  • Using MapBox embeds in place of Leaflets JS meant I could simplify adding interactive maps to pages. Well, almost. To display paths requires an additional layer to be created in TileMill – hopefully the ability to add vector lines in MapBox isn’t too far off.

With this code removed, the only behavioural enhancement required was for the responsive navigation. I’d like to thank Anthony Williams for helping me rewrite this using pure JavaScript. However, I’m still calling jQuery on pages displaying slideshows, so actively looking for an alternative that will allow me to shed this dependancy entirely.

Javascript: Bytes downloaded (requests)
Before 35.00 kB (2)
After 1.28 kB (1)

CSS

While helping out on a recent project at Clearleft, Mark introduced me to LESS, a CSS pre-processor I became eager to use here. With LESSphp compiling LESS on the server, comments are stripped out and the generated CSS is easier to compress, too.

By removing unused style rules and refactoring others, my raw stylesheet shrunk by 19kB. Yet you’ll note that the compressed CSS file is still larger that it was before. That’s because the small background noise texture shown on larger viewports has been embedded as a base64 string, removing a further request.

CSS: Bytes downloaded
Before 8.25 kB
After 9.02 kB

SVG

In February I began using an SVG image sprite, falling back to a PNG image for browsers that don’t support the vector format. To prevent both images loading, a subsequent update saw me move the following detection script into the <head>, before any CSS can be downloaded:

<script>
  (function flagSVG() {
    var ns = {'svg': 'http://www.w3.org/2000/svg'};
    if(document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1")) {
      document.getElementsByTagName('html')[0].className += ' svg';
    }
  })();
</script>

If support for SVG is detected, an svg class is added to the <html> element. This allows me to create rules like this:

.icon {
  background: url(/path/to/sprite.png) no-repeat 0 0;
}
.svg .icon {
  background-image: url(/path/to/sprite.svg);
}

Going further

Besides stripping out the metacruft added by software like Illustrator, further optimisation can be found by using the <defs> and <use> elements. These allow you to define common objects, reducing the number of shape descriptions appearing in your document.

To demonstrate how this works, I’ll use three icons from my sprite image: a grey RSS feed icon (#feed), a Flickr icon (#flickr) and an orange and white feed icon (#feeds). In my original file, each was defined separately:

<svg xmlns="http://www.w3.org/2000/svg">
  <g id="feed">
    <path fill="#999" d="M 4.73 13.13 C 4.73 14.15 3.90 14.98 2.87 14.98 C 1.84 14.98 1 14.15 1 13.13 C 1 12.10 1.84 11.27 2.87 11.27 C 3.90 11.27 4.73 12.10 4.73 13.13 Z"/>
    <path fill="#999" d="M 1 8.44 C 2.75 8.44 4.39 9.12 5.63 10.36 C 6.87 11.59 7.55 13.24 7.55 15 L 10.24 15 C 10.24 9.90 6.10 5.76 1 5.76 L 1 8.44 Z"/>
    <path fill="#999" d="M 1.00 3.68 C 7.24 3.68 12.31 8.76 12.31 15 L 15 15 C 15 7.28 8.72 1 1 1 L 1 3.68 Z"/>
  </g>
  <g id="flickr">
    <path fill="#eee" d="M 0 32 L 24 32 L 24 56 L 0 56 L 0 32 Z"/>
    <path fill="#06d" d="M 5 44 C 5 42.34 6.34 41 8 41 C 9.66 41 11 42.34 11 44 C 11 45.66 9.66 47 8 47 C 6.34 47 5 45.66 5 44 Z"/>
    <path fill="#f08" d="M 13 44 C 13 42.34 14.34 41 16 41 C 17.66 41 19 42.34 19 44 C 19 45.66 17.66 47 16 47 C 14.34 47 13 45.66 13 44 Z"/>
  </g>
  <g id="feeds">
    <path fill="#f93" d="M 0 80 L 24 80 L 24 104 L 0 104 L 0 80 Z"/>
    <path fill="#fff" d="M 8.73 97.13 C 8.73 98.15 7.90 98.98 6.87 98.98 C 5.84 98.98 5 98.15 5 97.13 C 5 96.10 5.84 95.27 6.87 95.27 C 7.90 95.27 8.73 96.10 8.73 97.13 Z"/>
    <path fill="#fff" d="M 5 92.44 C 6.75 92.44 8.39 93.12 9.63 94.36 C 10.87 95.59 11.55 97.24 11.55 99 L 14.24 99 C 14.24 93.90 10.10 89.76 5 89.76 L 5 92.44 Z"/>
    <path fill="#fff" d="M 5 87.68 C 11.24 87.68 16.31 92.76 16.31 99 L 19 99 C 19 91.28 12.72 85 5 85 L 5 87.68 Z"/>
  </g>
</svg>

Note how the square shape, the feed icon and the circles used within the Flickr icon are described multiple times. The <defs> element means we can define these just once and reference them later with <use> and the xlink:href attribute, like so:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <rect id="square" width="24" height="24"/>
    <path id="circle" d="M 0 3 C 0 1.34 1.34 0 3 0 C 4.66 0 6 1.34 6 3 C 6 4.66 4.66 6 3 6 C 1.34 6 0 4.66 0 3 Z"/>
    <path id="rss-icon" d="M 0 2.6797 C 6.24 2.6797 11.31 7.7598 11.31 14 L 14 14 C 14 6.2793 7.72 0 0 0 L 0 2.6797 ZM 0 7.4395 C 1.75 7.4395 3.39 8.12 4.6299 9.3594 C 5.87 10.5898 6.5498 12.24 6.5498 14 L 9.24 14 C 9.24 8.9 5.10 4.7598 0 4.7598 L 0 7.4395 ZM 3.73 12.1299 C 3.73 11.0996 2.8999 10.2695 1.87 10.2695 C 0.8398 10.2695 0 11.0996 0 12.1299 C 0 13.1504 0.8398 13.9805 1.87 13.9805 C 2.8999 13.9805 3.73 13.1504 3.73 12.1299 Z"/>
  </defs>
  <g id="feed">
    <use fill="#999" xlink:href="#rss-icon" x="1" y="1"/>
  </g>
  <g id="flickr">
    <use fill="#eee" xlink:href="#square" x="0" y="32"/>
    <use fill="#06d" xlink:href="#circle" x="5" y="41"/>
    <use fill="#f08" xlink:href="#circle" x="13" y="41"/>
  </g>
  <g id="feeds">
    <use fill="#f93" xlink:href="#square" x="0" y="80"/>
    <use fill="#fff" xlink:href="#rss-icon" x="5" y="85"/>
  </g>
</svg>

It’s easy to assume that gzip will take care of reducing file sizes, but manual optimisation beforehand can result in even larger reductions. For example, I was able to reduce my original SVG sprite (9.48kB, 3.36kB gzipped) to 7.34kB, which compressed down to just 2.84kB – comparable in size to the PNG sprite. 500 bytes seems like a small reduction, but using this technique on larger SVG images will have an even greater impact.

Image sprite: Bytes downloaded
PNG 3.42 kB
SVG Before 4.31 kB
SVG After 3.80 kB

Fonts

Earlier this year I cut the number of webfonts I was using from four to three by using a single font family. This reduced page download sizes a little, but changing my web font provider to Adobe Edge Web Fonts produced a far greater saving – although at the cost of being able to use Akagi (I’m now using Source Sans Pro). In fact, such was the reduction, I decided to include a forth font again, choosing the monospaced Source Code Pro – useful on code heavy pages such as this.

A free service without limitations or account management, Adobe’s new service is stupidly easy to set up. A single line of JavaScript provides a neat URL interface to various settings, and as the script includes WebFont Loader, there’s no need to add a chunk of JavaScript to the top of each page. Load times are brilliantly fast, and with fonts combined into a single file, the number of requests is the same regardless of how many you decide to use.

Of course, there is a trade-off here. Services like Fontdeck provide an extensive library of premium webfonts while free services like Adobe’s only offer a small selection of open source fonts. Yet with simpler set-up and greater performance, they’re an attractive option.

Webfonts: Bytes downloaded (requests)
Before1 325.9 kB (6)
After2 82.2 kB (3)

Other optimisations

I’m now serving content via CloudFlare, a smart service that optimises content and intercepts dubious requests. With this is place, I no longer need PHPminify for CSS and JavaScript magnification. It also acts as a CDN, so static content has been moved from Amazon S3 (which I discovered isn’t actually a CDN) back to this domain where it’s easier to manage.

Calling a single PHP include from each page allows me to specify the character set in the HTTP header. Adding the async attribute to my analytics script means this will now download and execute without blocking other assets.

There have been a few design related tweaks too. I simplified the IA by moving links to my articles and academic essays to within the Portfolio section. I’ve also increased the base font size on content pages from 16px to 18px.


In February, I concluded the results of my performance optimisation by including results from Google Page Speed, YSlow and webpagetest.org. This means I can measure the effectiveness of these latest changes. Both Google Page Speed and YSlow scores have increased by two points, to 96 and 98 respectively. Comparing results saved from webpagetest.org, the following improvements on the homepage can be recorded also:

Homepage comparison: 22 February 2012 v 30 October 2012
Requests
Before 23
After 15
Bytes downloaded
Before 500 kB
After 169 kB
Time to fully load document
Before 4.684 seconds
After 3.304 seconds

If I had more time, I would make the website quicker

Arguably, many of these optimisations are overkill, especially given some of the modest reductions. Still, this exercise was useful in understanding where performance gains can be found, and I can apply this knowledge on future projects.

Website optimisation can be a cruel game; everything has a number that begs to be reduced, but doing so requires a lot of experimentation, research and testing. And when you’re playing with the last hundred or so kilobytes, there’s little reward for your effort. Hopefully this overview will save you from playing the same game I have.

  1. Fonts used previously: Akagi Book, SemiBold; Magenta Book, Book Italic (served via Fontdeck) ↩︎

  2. Fonts used now: Source Sans Pro Light, Regular, Italic; Source Code Pro Regular (served via Adobe Edge Web Fonts) ↩︎

Discussion