Skip to content

February 25, 2014

Web Page Optimization – Changes I Made for Page Loading Speed

It has been years since I optimized the page loading speed at GoToQuiz. At that time, image sprites and CSS/javascript minification were the state of the art. In the intervening years, though, further progress has been made. Of particular note is the “retirement” of older versions of Internet Explorer, and the emergence of CSS3 and HTML5, which allows developers the ability to further streamline their sites. So as I am in the process of revamping the UI, I’m taking advantage of the latest techniques to boost page loading speed. Be aware, this is nothing revolutionary–this blog post is simply an overview of which changes I’m making, and why.

Migrating pages over to HTML5 allows the markup to be more semantic while taking fewer bytes–a double win.  Using tags such as nav, section and article helps alleviate so-called “div-itis”, allowing cleaner markup and a smaller overall filesize.  HTML5 also follows the maxim of “convention over configuration”, letting you eliminate unnecessary attributes like type=”text/javascript” on script tags, for example.

The adoption of CSS3 has provided many possibilities for creating beautiful visual effects without using image files.  Here is an example: on an early iteration of the design of the various headings on GoToQuiz, I used this sprite combined with CSS to create the rounded color bar appearance.

heading sprite

You can also see a gradient in there, as there were no CSS gradients at the time.  This was a pretty useful technique for its time, but of course it requires a fixed-size layout.  These were column headings, and the columns had to be exactly this wide or else the design wouldn’t work.  Later, as browsers began to adopt the border-radius property, I converted some pages over to use this improved sprite:

smaller header sprite

This time I used CSS to repeat the colors across (allowing for any width), and to create rounded corners in browsers that supported them.  But it would be so much better to eliminate the image completely!  That brings us to the present day, where the following CSS creates the effect I’m looking for:

h2 {
  color: #fff;
  background-color: #1f58a9;
  font: bold 1em/1 verdana, arial, sans-serif;
  padding: 0.46154em 0.30769em 0.46154em 0.61538em;
  margin-bottom: 0.07692em;
  box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.4), 0 2px 0 0 #c8c8c8;
  background-image: -webkit-linear-gradient(bottom, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0.18) 50%, rgba(255, 255, 255, 0.6) 100%);
  background-image: linear-gradient(to top, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0.18) 50%, rgba(255, 255, 255, 0.6) 100%);
  -pie-background: linear-gradient(to top, rgba(255, 255, 255, 0) 50%, rgba(255, 255, 255, 0.18) 50%, rgba(255, 255, 255, 0.6) 100%);
  border-radius: 8px 8px 0 0;
  border: 1px solid #1f58a9;
  text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.5);
  behavior: url(/css/pie2/PIE.htc);
}

Yes, I’m even taking care of outdated Internet Explorer there (using CSS3Pie)! The above CSS produces the following:

Example Heading

Changing the color is as simple as changing the background-color property. Now, these heading styles exist only as a snippet of text in the CSS file instead of relying on images. Of course, any gradient, rounded corner, shadow, button style, etc. can be changed over to CSS-only. Browsers that don’t support these techniques represent a very small share of the market at this point.

One issue I still had, though, was with the animated GIF for ajax loading. I wanted to replace that with CSS, too, so I went searching for a solution. Google turns up many results, but many involve special markup, and others just didn’t look that great or were overly complex. I decided to take and modify a simple one that someone had posted in a blog comment (sorry, I lost the link!). But here it is:

@-webkit-keyframes spin {
  from { -webkit-transform: rotate(0deg); }
  to {  -webkit-transform: rotate(359deg); }
}
@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(359deg); }
}

.wait {
  border: 5px solid rgba(31, 88, 169, 0.1);
  border-top-color: rgba(31, 88, 169, 0.5);
  border-radius: 50%;
  width: 40px;
  height: 40px;
  -webkit-animation: spin 0.6s infinite linear;
  -moz-animation: spin 0.6s infinite linear;
  animation: spin 0.6s infinite linear;
  text-indent: 40px;
  margin: 10px auto;
  overflow: hidden;
}

This produces a spinning circle animation in modern browers, while adding only 250 bytes to the CSS file after minification and compression. The animated GIF I had been using was 1.4KB and required an extra HTTP request.

I’ve managed to reduce the number of UI images on a page to three, which is as few as I can get. One is the favicon, which is unavoidable. Second is the site’s logo, again unavoidable. Third is a sprite of little icons which the UI calls for. Presently this sprite looks like:

icon sprite

Photoshop does not get maximum compression out of your images, even when using “Save For Web”. After searching for various image compressors, I believe TinyPNG is the best. It reduced my Photoshop-saved alpha PNGs to approximately half their file size. The image above went from 7KB to 3KB. After compressing them, I move the images to a CDN for even faster loading.

Enough about images, what about javascript? Firstly, I’m using this technique to serve the best version of jQuery:

<!--[if lt IE 9]>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<![endif]-->
<!--[if gte IE 9]><!-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<!--<![endif]-->

Versions of Internet Explorer below 9 will use jQuery 1.11 which still supports those browsers. All others will use 2.1, which has efficiencies gained from dropping support for IE < 9. I have this snippet near the bottom of the page, past all content. I don’t load jQuery asynchronously, because A) any other javascript that may follow depends on it, and B) it’s not blocking any rendering due to being placed after content. I have changed less important javascript to load asynchronously, however.

Here is the hierarchy of javascript loading precedence. This executes immediately:

(function() {
    //code here
})();

Using jQuery, this executes after the DOM structure is fully loaded (but before the onload event occurs, which happens when all page resources have downloaded):

$(function() {
    //code here
});

Note, above is equivalent to $(document).ready(function() {}); but less verbose. Finally, again using jQuery, this executes after the onload event:

$(window).on('load', function() {
    //code here
});

This last one is useful if you’re trying to get the shortest onload timings. It’s the ideal place for any social media plugins, for example. It might end up looking like this (condensed) code:

$(window).on('load', function() {
    var s,d=document,n='script',t=d.getElementsByTagName(n)[0],p=t.parentNode;
    s=d.createElement(n);s.async=true;s.src='http://apis.google.com/js/plusone.js';p.insertBefore(s,t);
    s=d.createElement(n);s.async=true;s.src='http://connect.facebook.net/en_US/all.js';
    d.getElementById('fb-root').appendChild(s);
    s=d.createElement(n);s.async=true;s.id='twitter-wjs';s.src='//platform.twitter.com/widgets.js';p.insertBefore(s,t);
    s=d.createElement(n);s.async=true;s.src='https://platform.stumbleupon.com/1/widgets.js';p.insertBefore(s,t);
    s=d.createElement(n);s.async=true;s.src='http://platform.tumblr.com/v1/share.js';p.insertBefore(s,t);
});

Social media plugins have the potential of really bogging down your page loads, but ensuring they load after onload means they are kept away from where they can do any harm.

So in conclusion…

These are some of the major optimizations I’ve added, in combination with old tried-and-true techniques such as minification, concatenation of CSS to a single file, setting far-future expiration headers, and preloading resources for the next page in a sequence. My goal is here is to achieve the most efficient loading of pages on GoToQuiz, with a first byte time of < 300ms and onload time of < 2 seconds.

Share a technique in the comments if you’d like!

Share your thoughts, post a comment.

(required)
(required)

Note: HTML is allowed. Your email address will never be published.

Subscribe to comments