Designing for Web Performance

Bibliographic Information

Designing for Performance

Publisher: O'Reilly Media, Inc.
Pub. Date: December 14, 2014
Print ISBN-13: 978-1-4919-0251-6

Replacing Image Requests

In addition to decreasing your images’ file sizes, it’s also important to decrease the number of image requests on the page to improve page load time (read more about the basics of page load time in Chapter 2). Being deliberate about how you are requesting and loading images on your site will help you optimize both the total page load time and how quickly your users can begin to see and interact with your site. There are two main ways to eliminate image requests:
  • Combine images into sprites
  • Replace image files with CSS3, data URIs, or SVG versions

Sprites

A common saying in the world of web performance is “the fastest request is a request not made.” One way to reduce the number of image requests on your page is to combine images into sprites. Your page weight will increase slightly because you’ll have one large image file and additional CSS to position and show the graphics within the image, but it’s likely that combining images into a sprite will be a win for your site’s page speed.
The best candidates for sprited images are small, repeated images incorporated into your site design. This may include icons, the site logo, and other CSS background images that are used around your site. Figure 3-15 is an example of a sprite.

This example sprite.png file contains a logo and heart, star, and other icons that we can use throughout a site’s design.

Figure 3-15. This example sprite.png file contains a logo and heart, star, and other icons that we can use throughout a site’s design.
You can see that this sprite includes a main logo as well as various versions of stars and other icons. Let’s implement parts of this sprite using CSS and HTML. Figure 3-16 shows what we want the output to look like.

This screenshot shows how we want our sprite to be used on the page.

Figure 3-16. This screenshot shows how we want our sprite to be used on the page.
Without a sprite, we have individual images applied to each element. Here is our starter markup:

Designing for Performance

We have a favorite!

We have a winner!!

In this HTML, we are going to apply the logo to the h1 element, one of the stars to the first paragraph with the class fave, and a different class to the paragraph with the additional class of winner. Here is our starter CSS with each individual image applied:
h1, .fave:before {
background: transparent no-repeat; ➊
}
h1 {
background-image: url(h1.png);
text-indent: -9999px; ➋
height: 75px;
width: 210px;
}
.fave {
line-height: 30px;
font-size: 18px;
}
.fave:before { ➌
background-image: url(star.png);
display: block;
width: 18px;
height: 17px;
content: '';
float: left;
margin: 5px 3px 0 0;
}
.winner:before {
background-image: url(star-red.png);
}
  1. We are applying a transparent background-color to these elements, and we’re telling it to not repeat the background-image across the entire width and height of the elements.
  2. We use text-indent to move the text within the h1 off the visible area of the page and to allow the background-image to show through. There are a number of ways to move text off the visible section of the page but still make it available to screen readers; you can also try the following method for hiding visible text:
    This text-indent: 100% method may also significantly increase performance on the iPad 1 in cases where you are applying many animations to this element.
  3. To get the star to show up to the left of the paragraph text, I’m applying the image to the :before pseudoelement for the paragraph. The :before selector creates a new, inline element so you can insert content before the content of the selected element. :after is also a psuedoelement you can use. These pseudoelements are supported in modern browsers, with partial support in Internet Explorer 8 and no support in earlier versions of Internet Explorer.
Let’s change this over to use a sprite instead of individual images. We’ll use the preceding example sprite (Figure 3-15) and apply it to the h1 and .fave:before elements:
h1, .fave:before {
background: url(sprite.png) transparent no-repeat;
}
Figure 3-17 shows what our new paragraph styling looks like with the sprite applied to the :before element.

This screenshot shows our paragraphs with the sprite applied to the before element, but without proper placement.

Figure 3-17. This screenshot shows our paragraphs with the sprite applied to the before element, but without proper placement.
Now we need to determine the new background-position of our sprite so that the stars appear. The h1 received the default background-position of 0 0, or the top left of the sprite. background-position can accept different kinds of value pairs that correspond to the x-axis and y-axis, respectively:
  • 50% 25%
  • 50px 200px
  • left top
In our case, we know where in the sprite our star images are, so we can use pixels to move the background-image over until each star shows. For the first star, we need to move the sprite 216px to the left and 15px up to show the sprite in our :before pseudoelement. We’ll apply the following CSS to .fave:before in addition to its existing styles:
.fave:before {
...
background-position: -216px -15px;
}
Our second star will automatically receive all of the styles we applied to the first, as both paragraphs share the class fave. We just need to choose a new background-position to show the red star icon:
.winner:before {
background-position: -234px -15px;
}
Here’s our final CSS with the sprite applied instead of individual images:
h1, .fave:before {
  background: url(sprite.png) transparent no-repeat;
}
h1 {
  text-indent: -9999px;
  height: 75px;
  width: 210px;
}
.fave {
  line-height: 30px;
  font-size: 18px;
}
.fave:before {
  display: block;
  width: 18px;
  height: 17px;
  content: '';
  float: left;
  margin: 5px 3px 0 0;
  background-position: -216px -15px;
}
.winner:before {
  background-position: -234px -15px;
}

CSS3

Another way to decrease image requests is to replace them with CSS. You can create shapes, gradients, and animations using CSS. For example, CSS3 gradients:
  • Can handle transparency
  • Can be overlaid on a background color
  • Eliminate an image request
  • Are super easy to change

Data URIs and Base64-Encoding Images

Replacing very small, simple images with data URIs is also a way to decrease the number of requests a web page has to make. To do this, change an image to a URI by converting it to text using a method called Base64 encoding. For example, let’s say we have a PNG-8 image of a small triangle (Figure 3-23) that we want to reuse in various places across a site.
Figure 3-23. Small triangle in PNG-8 format.
We can convert the image to its text equivalent (a data URI) using an online Base64 encoder. We upload our image and the encoder returns a data URI for us to use in our CSS. The result of Base64-encoding this triangle and applying it to the background-image of an element using CSS would look like this:
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUh
EUgAAAAoAAAAQCAAAAAAKFLGcAAAAVUlEQVR4AWM4/B8GGOyfw5m6UQimx3
Y4c6PKTxjzUn4FnPmB7QaM+X+CDZz5P2E+nHlS6C2M+b86Ac78b3MYzlyq8
hPG/J/fAmSegQC22wzhxlBQAQBbjnsWelX9QwAAAABJRU5ErkJggg==);
Using Base64 to encode images saves an HTTP request by embedding the code for the image, which is generally a good rule of thumb for performance. It allows the image to be processed and display the image immediately rather than wait for the HTTP request for the image.
However, inlining images also removes your ability to cache the file, and it also makes your CSS larger (sometimes substantially, depending upon the length of the data URI). Be sure to measure the performance consequences of changing any images to data URIs before permanently implementing them on your site to make sure they’re actually a performance win for you.
Searching for unused styles:
There are a number of tools currently available to you for finding potential CSS to eliminate. Dust-Me Selectors (http://www.brothercake.com/dustmeselectors/) is a browser plug-in for Firefox and Opera that can scan your website’s HTML to find unused selectors. In Chrome DevTools, there is an Audits tab (Figure 4-2) that will allow you to run a Web Page Performance audit and see a list of unused CSS rules.
CSS also allows you to leverage the power of shorthand style declarations. Shorthand declarations, such as background, include many individual style values in one line. The background declaration, for example, includes:
  • background-clip
  • background-color
  • background-image
  • background-origin
  • background-position
  • background-repeat
  • background-size
  • background-attachment

Remove Specificity

When it comes to CSS, specificity is the term for how you write out selectors to help a browser determine which CSS rules are applied. There are different kinds of selectors, and each carries its own weight; specificity is calculated by a formula (http://bit.ly/1ttWQGk) based on these selectors. If two selectors apply to the same element, the one with higher specificity wins.
One additional benefit of hosting the font yourself is customization of the font file. If you are hosting your own web font, you can run it through a tool like Font Squirrel’s Webfont Generator (http://www.fontsquirrel.com/tools/webfont-generator) and choose a custom character subset to optimize the font file, as shown in Figure 4-3.
Pattern Libraries
Javascript loading
If you make a call to a JavaScript file rather than inlining the script within your HTML, your user’s browser needs to go request that file from your server (or a third party’s server, if it’s a resource you’re calling from another site). This could add tens to thousands of milliseconds of wait time before the HTML parser can continue rendering the DOM. However, you can indicate to the browser that this script doesn’t need to be executed right away, and therefore shouldn’t block content rendering, by adding the async tag to your script:

Minification and gzip

You can use command-line tools for minifying your code, or online tools like CSSMinifier.com (http://cssminifier.com/) and JSCompress.com (http://jscompress.com/). As shown in Figure 4-8, I pasted my site’s CSS file into the tool on CSSMinifier.com and it output minified, optimized, and shorter CSS for me to implement on my site. The output was 15% smaller than the original file.
To implement gzip compression, you need to enable it on your web server. How to do this depends on your server type:
gzip is great for all kinds of text files like stylesheets, HTML, JavaScript, and fonts. The only exception to this is WOFF font files, which come with built-in compression.

Caching Assets

Caching is critical for your site’s performance; assets that are cached do not need to be requested again from your server, saving a request. Caching works by sharing information with a user’s browser so it can determine whether to display the previously downloaded (cached) file from disk, or request the asset again from the server.

This information is communicated in an HTTP header, which is the core part of any request sent back and forth between a browser and your server. HTTP headers include lots of additional information like a browser’s user agent, cookie information, the type of encoding used, the language the content is in, and more. There are two kinds of caching parameters that can be included in a response header:

  • Those that set the time period during which a browser can use its cached asset without checking to see if there’s a new one available from your server (Expires and Cache-Control: max-age)

  • Those that tell the browser information about the asset’s version so it can compare its cached version to the one that lives on the server (Last-Modified and ETag)

You should set one of Expires or Cache-Control: max-age (not both), and one of Last-Modified or ETag (not both), for all cacheable assets. Expires is more widely supported than Cache-Control: max-age. Last-Modified is always a date, and Etag is any value that uniquely identifies the version of the asset, such as a file version number.

All static assets (CSS files, JavaScript files, images, PDFs, fonts, etc.) should be cached.

  • When using Expires, set the expiration up to one year in the future. Don’t set it to more than one year in the future, as that would violate the RFC guidelines.

  • Set Last-Modified to the date on which the asset was last changed.

If you happen to know when a file is going to change and you’d like to set a shorter expiration, you can do so, though a minimum of one month is still best practice. Alternatively, you could change the URL reference to the asset, which will break the cache and force the user’s browser to fetch a new version.

For a guide on enabling caching with an Apache server, read the Apache Caching Guide (http://httpd.apache.org/docs/2.2/caching.html). For a NGINX server, read NGINX Content Caching (http://nginx.com/resources/admin-guide/caching/).

Images

RESS, which stands for responsive web design with server-side components, is one option for creating and serving correctly sized images. You can improve performance by choosing which assets to serve to your user on the server-side, rather than optimizing them on the client-side. Your server can make smart decisions by looking at a user agent string, from which it can guess things like your user’s screen size, device capabilities like touch, and more. Tools like Adaptive Images (http://adaptive-images.com/) detect your user’s screen size and will automatically create, cache, and deliver correctly sized images based on your defined breakpoints (see Figure 5-3). In his book High-Performance Responsive Design (O’Reilly), Tom Barker outlines a number of RESS techniques and how to implement them.

Primarily, you need to ensure that only the appropriate content is being loaded at each breakpoint. Don’t join the other 72% of websites (http://bit.ly/1tBv6cT) that are serving up the same size responsive design site across screen sizes.

If you’re able to, implement automated tests that measure the total page weight for each of your chosen breakpoints. Tom Barker included an excellent chapter on continuous web performance testing in his book High-Performance Responsive Design, which outlines how to implement Phantom JS tests that measure each breakpoint’s performance, including YSlow score and total page weight.

You can also test this manually. Emulate a device using Chrome DevTools and use the Resources panel to see which image size is being downloaded for that device. Here is an example set of media queries in which I choose to serve a different image based on breakpoint:

@media (min-width: 601px) {

  section {

    background: url(big.png);

  }

}

@media (max-width: 600px) {

  section {

    background: url(small.png);

  }

}

Real User Monitoring

After choosing a real user monitoring tool, identify your site’s major pages to see how they perform for your users over time. The home page, top landing pages, any kind of checkout flow, and other high-traffic, important areas of your site should be included in your main reports. As you look at your users’ load time for these pages, segment the data in a few different ways to get a more holistic picture of your end-user experience:

  • Geographic location (near/far from a data center, areas where your main audience lives)

  • Network type (cellular, WiFi, etc.)

  • Median as well as 95th percentile total page load time

Why 95th Percentile?

The 95th percentile metric is another way to illustrate the performance pain points on your site. The median will give you a general understanding of how long a page might take to load for your user, but the 95th percentile metric is important to ensure that the vast majority of your users have an excellent user experience. The 95th percentile is the slowest 5% of your page views, but 5% is still a notable part of your user base. For RUM, 95th percentile tends to be a measure of how slow your users’ network connections are, and slower connections are always going to send the higher percentile through the roof. Note that Google Analytics provides averages for page load time, not percentiles.

Once you have this data, begin to analyze the differences between audience groups, like in Figure 6-11. How different is the median page load time from the 95th percentile? How does the site perform for people in other countries? How about your users on mobile devices? Are there major differences in load time between your top five pages?