Chapter 8. Performance

CSS files are (typically) small files with (typically) simple syntax. It might seem that where performance is concerned there is little to be done to make a difference, and that improvements will be slight. In many cases, this may be true. However, when we are dealing with CSS at a grander scale, where our files may be reaching larger sizes and we expect them to be served millions or tens of millions of times a day, small improvements make big differences—both to the user and the developer. One kilobyte may seem a tiny amount of data by today's standards, but do the math—those kilobytes soon add up to gigabytes of data that the business needs to pay for in bandwidth. And when we are considering the payload the user downloads and the speed of the page rendering, it is a truism to say that every little bit counts.

There are several angles we must look at when concentrating on performance. From the user's perspective, it is important that the files are small, can be well cached (and hence loaded more quickly) and that the files are up to date. From the browser's perspective, we want our CSS to be as efficient as possible and for the content to be rendered (and re-rendered in response to interaction or animation, if necessary) as fast as possible. From the businesses' perspective, we want to serve from the user's cache (primarily, and our server cache secondarily) as much as possible and keep the amount we are sending to (and receiving from) the user to a minimum, while still being sure the user has the latest version of our code.

In this chapter, we will concentrate on how to improve performance from these three distinct angles, and you will learn several important things about each. You will learn about the following:

  • File size concerns and best practices

  • Having fewer HTTP requests is more important than file size

  • Caching strategies

  • Browser rendering and blocking

The Payload—Worry About File Size

Best practices in CSS require us to consider the number of characters we enter and the implications of them. Every character counts. Although high speed Internet is more widespread these days, as an author of CSS on a high-traffic website, you have many more demographics to worry about than most other companies. As such, your users may be on dial-up Internet access (out of choice or because of their location), using mobile devices in areas with poor reception, in countries far from the point of origin of your site (your servers), or any combination of these. Preprocessing may be taking place at many levels such as their ISP, firewalls and routers at their location, or firewalls and routers at levels even higher up the pathway of data. One of our primary concerns with our data reaching these machines as quickly as possible is the amount of data that we are sending. When we send data via TCP/IP (Transmission Control Protocol and Internet Protocol), the networking protocol typically used by the Internet, our information is grouped into bundles called packets. On networks there is a concept of maximum packet size, or maximum transmission unit (MTU), typically 1500 bytes on an Ethernet network. Having a packet that exceeds this size incurs a performance penalty (how big a penalty is dependent on many factors: MTU, packet size, network speed, and so on). Since we cannot be certain what the MTU of any particular network is—and even if we did, knowing which packet will exceed the limit is very difficult—to avoid this packet boundary all we can do is our very best to provide the smallest amount possible.

Users of the Internet are more fickle than, say, users in a shopping center. Deciding that a website is too slow and browsing to another affords instant gratification, whereas finding another store that sells saucepans requires a commitment from the user to leave the current store, locate another store, locate the product, and so on. The goodwill the user has toward our website is finite, and we must do whatever we can to keep the user happy and moving toward whatever our business goal is—be it buying products or services, viewing pages, or consuming content.

Keeping file size down and speed up isn't just good for our visitors. It's good for our business too—the less data we serve, the fewer bandwidth costs we incur. And furthermore, Google and other search engines care about how quickly our pages load. Performance of websites is fast becoming an important factor for effective SEO strategies. Additionally, Internet Explorer (IE) versions 7 and below cannot cope with CSS files above 288 KB.

Note

With CSS files exceeding 288 KB, IE 7 and below will only parse the first 288 KB of the file. Although it is possible to break files larger than this into multiple files (with the performance implications of more requests), it is obviously preferable to keep our CSS smaller in the first place.

So what can we do to keep our file sizes down?[61]

Naming Conventions

As we have said before, it's important to come up with rules on how classes and IDs are named, and to adhere strictly to them. Unless there are good reasons not to do this in your organization or your developers are particularly opposed to it, we recommend naming your classes and IDs in camel case, as follows:

  • mainContent

  • heroImage

  • emailAddress

Using camel case should help keep our code easy to read while avoiding the extra characters that hyphens, underscores, or other delimiters incur. If namespacing your code via prefixes, try to avoid verbose prefixes like these:

  • ourCompanyHeroImage

  • ourCompanyEmailAddress

Instead, consider abbreviations or acronyms; for example:

  • ocHeroImage

  • ocEmailAddress

If these names are hard to read, we can now consider using a delimiter, safe in the knowledge that our prefix is still smaller than the alternative.

  • oc-HeroImage

  • oc-EmailAddress

Although we want our naming system to be semantic, we could also consider abbreviating it, as long as it is still easy for the developers to read:

  • heroImg

  • emailAddr

The downside of this approach is that there are many acceptable abbreviations for words, and developers may find the naming less intuitive, resulting in much flicking back and forward through the code, or inconsistent naming techniques. We recommend (as always) discussing what is acceptable within your organization for the best results, adding the technique to your CSS Coding Standards Guide and implementing it strictly.

File Names

File names are used throughout our code to link files together and reference them. To keep our file sizes really small, it is tempting to name our files a.css, b.css, and so on. In fact, as long as the files are being served with the correct mime type,[62] we could even name them a.c or just a. In practice, though, we would suggest that this creates too much of a cost in legibility and makes code difficult to write and files difficult to locate. Although file names should not be verbose, they should be long enough to be clear and easy to understand. Here is an example of a file being referenced in CSS:

input {background: url("/images/shadow background.gif");}

The double quotes in the URL are not usually required. They are included in this instance because of the space in the file name, which means quotes are required so that the browser understands the space is part of the file name. However, when naming files, we recommend you always use hyphens instead of spaces. We recommend this for a few reasons:

  • Google will treat a hyphen in a URL path as if it were a space, but not other characters. If we name our file "shadow background.gif," Google comprehends this as being "shadow background.gif". Good SEO on images used for presentation rather than content (like background) may seem unnecessary, but Google image searches account for a surprising amount of traffic, and anything that might bring users to our site is a positive thing.

  • Not using spaces means we don't need the quotes in the URL value, saving us two characters.

  • Using spaces means the browser needs to intelligently process them and change them to %20 in the URL (a space, URL encoded) and older browsers may not support this. You could, of course, place the encoded space directly in the URL, but this would result in two extra characters in every instance.

We also recommend you always name your files in lowercase, even if using a web server/operating system that is not case-sensitive. It is an easy rule to follow and will make your files more portable should you decide to switch web servers at a later date. Avoiding symbols and unusual characters will also ease transitions from one server to another and avoid unexpected issues. Although using dots in the file name has become common practice for versioning (as in jquery-1.4.4.min.js), you should avoid placing them at the beginning of the file name since Linux-based operating systems will interpret that as a hidden file. As a matter of best practice, it is best to stick to Latin alphanumeric characters, hyphens, and dots.

Folder Structure

At first glance, folder structure may seem to have little impact on file size. This, however, is untrue. Folders are referenced often throughout CSS code, be it pointing to background images, fonts, behaviors, or importing other stylesheets.[63]

The simplest way to minimize the characters used up by referencing folders is to keep all your files in the same directory as your CSS. This technique will quickly become unusable in a website of any size, however, and as always, we should aim for a good balance between what is a good practice and what will make a developer's life a nightmare.

Here is an example of a file being referenced in CSS:

input {background: url("/images/shadow background.gif");}

The first character in our URL is a forward slash, which means begin at the root of the URL. So, for a CSS file located at www.thedomain.com/css/style.css, this URL will point to www.thedomain.com/images/shadow%20background.gif. All URLs in CSS are relative to where the file containing the CSS is located, so suppose we changed the CSS to the following (with the slash omitted):

input {background: url("images/shadow background.gif");}

The URL would then point to www.thedomain.com/css/images/shadow%20background.gif.

Since any files we reference with CSS are for use primarily by CSS, it makes sense to keep them in a subfolder inside the same folder our CSS is residing in. This makes our CSS more portable since we can copy or move the CSS folder, and all the file paths and references will be intact. It also saves us the forward slash in the URL, or ../, to jump up a folder and locate the files we are looking for.

Often, folders are named as the plural of what they represent, such as these:

  • images

  • assets

  • fonts

By always using the singular instead, it is easy to save characters and to enforce this naming technique across our business. In folder names, abbreviations are much safer to use since our entire folder structure (at least at the high level) is likely to be very predictable, and exactly the same from project to project. This is yet another thing that you would do well to add to your CSS Coding Standards Guide:

  • img

  • asset

  • font

Our line was originally 58 bytes, as follows:

input {background: url("/images/shadow background.gif");}

It is now 52 bytes, as follows:

input {background: url(img/shadow-background.gif);}

This is a 10 percent reduction. This might seem a small change, but if there were 100 lines similar to this in our document, we would now have saved 60 bytes, which translates into many more when we consider the amount of requests we are responding to.

You should also feel free to use common and well-understood abbreviations in your filenames, which can further reduce our CSS:

input {background: url(img/shadow-bg.gif);}

Our asset folder is simply a catchall folder for any files we want to reference that don't fall easily into another grouping, such as Shockwave Flash (SWF), multimedia Adobe Flash files. With all this in mind, let's take a look at an example folder-naming strategy:

/
      css
         img
         font
         asset
      img
      js
      asset

If you think about all the places where we are referencing these files, this all translates into smaller files, faster downloads, and less bandwidth.

Syntax

There are many parts to CSS syntax that we include for legibility that are simply not necessary. Since these can often be minimized via minification tools (read more about these later in this chapter), it is safe to keep these in your CSS whilst writing it if you are using them. However, it is important to know about these and the implications of them.

Whitespace

There are very few whitespace characters that are actually required by CSS. The spacing between parts of our selectors is necessary, as is the spacing between items referenced in shorthand CSS, or properties that take multiple parts as their values. Everything else exists purely to make it easier for us as developers to read and scan our code.

Note the following code:

body
{
        font-family: arial, sans-serif;
        font-size: 16px;
        background-color: #f4f4f4;
}

.clear
{
        clear: both;
}

From a browser's perspective, the preceding code is exactly the same as this:[64]

body{font-family:arial,sans-serif;font-size:16px;background-color:#f4f4f4;}.clear{clear:both;}

Although the second example is harder to read, it is a significant saving in file size. Some of this is worth explaining succinctly:

  • The space between the selector and the opening brace is unnecessary.

  • The space between the colon following the property name and the value is unnecessary.

  • The space between comma-separated values is unnecessary.

  • The carriage return after the closing brace is unnecessary.

  • Spaces before !important are unnecessary (and any between ! and important).

In all of these instances, you could use other whitespace instead of space, such as tabs. As you can see, there are many savings to be had here. We do not recommend you write your CSS in this fashion as it would be impossible to manage (see the section on minifying your code later in this chapter).

Note

When using fonts with spaces in their names, you should surround them in quotes (either double or single) according to the CSS specifications. Although many browsers will have no problem with the quotes being omitted, others will not read the value properly.

The Last Semicolon

The final semicolon after the last property and before the closing brace is unnecessary and easy to omit without affecting legibility.

Note the following:

.clear {clear:both;}

And the following:

.clear {clear:both}

Both of these lines are exactly the same as far as the browser is concerned, and save a single character for every rule.

Background Colors

When specifying a background color, rather than stating it (in perhaps a more correct fashion) as follows:

background-color: blue;

it is safe to instead use the following:

background: blue;

This saves six characters in every instance. Be aware, though, that this technique overrides the other properties set in this shorthand property and may have unintended effects.

Zeroes and Units

Since a zero is nothing regardless of the unit you are using, it is unnecessary to use a unit in this instance. Note these two properties:

border-width: 0px;
margin-top: 0em;

They can safely be written as follows:

border-width: 0;
    margin-top: 0;

This would have no effect on the rendering of the page whatsoever.

Cancelling Borders

When removing borders, instead of stating the following:

border-width: 0;

Or the following:

border: none;

You can instead safely put the following:

border: 0;

Again, from the browser's perspective, this results in no border and fewer characters.

Zeros and Decimal Places

When using decimal places beginning with zero, the zero can be safely omitted. In other words, instead of stating the following:

font-size: 0.12em;

It is safe to instead put the following:

font-size: .12em;

These two statements are exactly equivalent and save a character in each instance, though they may be harder to read for some developers. This action is taken on your behalf by most CSS minification tools, so if you find legibility is affected and you are using these tools, it is safe to keep the zero in.

Margin/Padding Shorthand

Note the following CSS:

margin-left: 10px;
margin-right: 10px;
margin-top: 10px;
margin-bottom: 10px;

This can also be represented as this (in the order top, right, bottom, and left):

margin: 10px 10px 10px 10px;

It can also (since all four values are the same) be represented as this:

margin: 10px;

If you provide three values, the first represents the top, the second both horizontal values, and the third the bottom. Note the following CSS:

padding-left: 5px;
padding-right: 5px;
padding-top: 20px;
padding-bottom: 10px;

It is the same as this:

padding: 20px 5px 10px;

Additionally, if you only provide two values to the margin or padding properties they represent the vertical and horizontal value, respectively. Note the following CSS:

padding-left: 10px;
padding-right: 10px;
padding-top: 20px;
padding-bottom: 20px;

Which is exactly equivalent to this:

padding: 20px 10px;

Which is also exactly equivalent to this:

padding: 20px 10px 20px;

And this:

padding: 20px 10px 20px 10px;

Many other shorthand properties exist that are outside the scope of this book for border, border-radius, list-style, font, and many others. It is worth spending some time researching and documenting them, and ensuring that the rest of your team members are proficient in using them. As well as making your CSS files smaller, they can also make your CSS easier to read and scan. However, you need to be aware of caveats to this approach since a shorthand property overrides all the subproperties that make it up and may have unexpected consequences.

Colors

Every browser has a series of named colors (defined in the spec for HTML 3.0) that it understands and will render appropriately. These include the following:[65]

  • aqua

  • black

  • blue

  • fuchsia

  • gray

  • green

  • lime

  • maroon

  • navy

  • olive

  • purple

  • red

  • silver

  • teal

  • white

  • yellow

There are many more colors that browsers support, but which are not part of the W3C standard (www.w3.org/TR/css3-color/#html4). However, only eight of these colors are "absolute" colors, meaning they include a value of zero or 255 for each of their red, green, and blue values. These are the following:[66]

  • aqua (#00ffff)

  • black (#000000)

  • blue (#0000ff)

  • fuchsia (#ff00ff)

  • lime (#00ff00)

  • red (#ff0000)

  • white (#ffffff)

  • yellow (#ffff00)

Out of these colors, only the following are not open to interpretation:

  • black (#000000)

  • blue (#0000ff)

  • red (#ff0000)

  • white (#ffffff)

  • yellow (#ffff00)

We therefore recommend that these are the only ones to be used in your CSS. They are easier to read and comprehend, and in many cases shorter in characters than their hex counterparts (unless you use shorthand colors (mentioned below); in all cases, they are shorter than their RGB counterparts).

However, there are further savings that can be achieved. Colors represented as hexadecimal (referred to as "hex colors") are specified as a triplet (meaning they are made of three parts). Each part of the triplet is a hexadecimal byte: a value with 256 variations, in this case from 0 (00) to 255 (ff). The triplet itself has 16,777,216 variations. If each part of the triplet is formed of two identical characters, we can shorten these into one. For example:

  • #112233 can be represented as #123

  • #00aaff can be represented as #0af

  • #99aa00 can be represented as #9a0

The decision to use named colors or hex colors is to be made within your organization. Where using hex colors it is safe to use the shorthand versions. You may decide to use named colors in the instances that they are appropriate, since they are easier to read, but any seasoned CSS developer will be so used to seeing #000 and #fff that there is no problem sticking specifically to hex colors in that instance. Although RGB colors are defined in the CSS2.1 specification, CSS3 extends this to support RGBA as well as HSL (Hue, Saturation, Lightness) and HSLA (Hue, Saturation, Lightness, Alpha). These accept non-hex values—the RGB values are integers between 0 and 255, and Alpha is a number between 0 (transparent) and 1 (opaque). Hue is in degrees (0 to 360), while Saturation and Lightness are percentages. Although all these values are more verbose (and add more file size) than hex colors, you may find them more useful to implement.

Note

If using RGBA, HSL, or HSLA color definitions in your CSS, make sure to provide an RGB or hex color fallback for browsers that do not support them. Also, if you are using the filter attribute in IE (possibly for cross-browser support with things like gradients, opacity, and so on), be aware that this attribute does not support shorthand colors, RGB, RGBA, HSL, or HSLA. Some older mobile browsers had issues with shorthand colors such as these. However, these browsers have all but disappeared, and even for sites with the highest traffic, we do not recommend being concerned with this demographic.

As a final example that uses many of these syntactical methods to save on file size, see the following CSS, weighing in at 146 bytes:

.our-company-main-content
{
   background-image: url("../../images/shadow background.gif");
   border: none;
   margin-top: 0px;
color: #aabbcc;
}

This can easily be shortened to 110 bytes:

.oc-mainContent {
   background-image: url(img/shadow-background.gif);
   border: 0;
   margin-top: 0;
   color: #abc
}

There is no sacrifice in legibility, and yet we have saved 36 bytes, which is a 25 percent decrease—even before minifying the file.

Minimizing Image Sizes

It is vital to understand what image types are appropriate in which instance and how to keep their sizes down. In the following sections, we will discuss the three primary types of images you will use.

GIF (Graphics Interchange Format)

GIFs are limited to a maximum of 256 colors and support transparency, but not alpha transparency. This means that by using up one of our available 256 colors, we can set pixels to be completely transparent or completely opaque (nontransparent), but nothing in between (semitransparent). They are typically appropriate for button images, items with hard edges and images where color accuracy is important. GIFs are a lossless compression format, which means that as long as you are using 256 colors or fewer (including transparency) there should be no degradation of image quality. When saving GIFs, use your image editor to ensure you minimize the amount of colors in use and turn off transparency if you don't need it. GIFs can contain multiple frames and information to animate between and loop these frames for simple animations (which increase file size dramatically and should be used sparingly).

JPEG (Joint Photographic Experts Group)

JPEG compression is lossy, which means information is lost the more the image is compressed. Your image editor should include a slider or similar means of adjusting the amount of compression when you come to export your image. As you decrease the quality (increase the compression), the file size will decrease, but artifacts will become visible in the image. It is up to the designer to decide on an appropriate and visually acceptable level of compression, typically 80 percent to 90 percent is the best compromise and should result in artifacts that are not obviously detectable. JPEGs support 16 million colors and are best suited to photographs or complex images with many colors. JPEGs do not support transparency at all.

PNG (Portable Network Graphics)

PNG is a lossless format, typically with a better compression rate than GIF files. They can be saved with different color depths, meaning they can support as many colors as is appropriate for the current image. They also support alpha transparency, meaning pixels can be made semi-transparent. PNGs are generally appropriate as a replacement for GIFs, although animated PNGs do not have widespread support yet. When saving PNGs it is important to set the color depth to something appropriate for the image.

Note

Although IE 6 does not support alpha transparency, there are many hacks available to fix this using JavaScript, behavior files, filters, and mixtures of these. Our recommended approach is the Fireworks hack, or 8-bit PNG hack. If you save a PNG with alpha transparency in Adobe Fireworks as 8-bit (which is effectively a bug in the software), the result is that it displays properly in all browsers, except for IE 6, which makes any semi-transparent pixel completely transparent. This is often an acceptable compromise as long as your designers work with this constraint in mind.

Your image editor should have tools to help you minimize the file size of any of these files, and see the difference that various file formats, compression rates and color depths will have. However, even after this, there is likely to be extraneous information in your image files. Comments, metadata, and unnecessary color profiles may be left in your files. There are many individual tools for addressing these concerns, but ImageOptim (available at http://imageoptim.pornel.net/) is a free, open-source app for OS X that combines all of these. It often causes savings in file size of 30 percent to 35 percent. Implementing a tool like this in your build process is a worthwhile thing to do and should be seriously considered. Various Windows and Linux alternatives are available (albeit less fully featured) but you should be able to achieve the same result using multiple command-line utilities in your build process.

Minifying

Using the techniques outlined so far in this chapter, it would not be difficult to write your own CSS minification script, which automates all of them on your behalf. However, there are many things that would catch you out in that process. Fortunately, methods already exist to achieve this for you. The most common of these is YUI Compressor.

YUI Compressor is a Java-based tool for minifying JavaScript and CSS files that can be downloaded at http://github.com/yui/yuicompressor/. It automates tasks on our behalf, taking care of many of the methods of minimizing file size we have already discussed in this chapter, meaning that we can ignore them in our development code, but still reap the benefits in our production code. Once it is installed, the syntax is very easy to use. From the command prompt, the syntax is the following (where x.y.z. is the version number):

java -jar yuicompressor-x.y.z.jar [options] [input file]

Potential options that apply to CSS are:

--line-break

This option allows us to break our CSS into lines after a particular column (a column in this instance meaning a particular character on a line). For example, setting this to 1000 will insert a line break at the first opportunity after the one-thousandth character of every line. There are many cases where you might need this option, particularly for debugging (you can read more about debugging in Chapter 10). Set this to 0 to insert a line break after every rule.

--type

This option allows us to specify the format of the input file. This option should only be set if our file suffix is not .css (or .js), in which case we should always set this to css for our CSS files.

--charset

This option allows us to specify the character set of our input file. If you do not supply it, the default character set encoding of the system that the YUI Compressor is being run on is used. Although unnecessary in most cases, if you are using UTF-8 characters (for example, in the content property), this option should be set.

-o

This option specifies the name of the file we want to output to.

Here is an example command:

java -jar yuicompressor-x.y.z.jar style.css -o style.min.css

YUI Compressor performs the following functions on CSS files:

  • Strips all comments[67]

  • Removes unnecessary whitespace

  • Removes the last semicolon from each rule

  • Removes any extraneous semicolons

  • Removes any rules with no properties

  • Removes units on any values of zero

  • Removes the leading zero on any values with decimal places that begin with zero

  • Consolidates like properties into one, in margin, padding, and background position

  • Converts any RGB colors into hex colors[68]

  • Removes extraneous character sets[69]

  • Shortens alpha opacity statements using the filter property to the IE4 style[70]

If you are using YUI Compressor, you can rest safe in the knowledge that all these actions will be performed, and, if you wish, not worry about them in your unminified code.

If you have used any hacks (many of which are detailed in Chapter 3), it is useful to be aware that YUI Compressor is tolerant of many of them and will not attempt to minify or otherwise break them. Accepted hacks include the following:

  • The underscore hack

  • The star hack

  • The child selector hack

  • The commented backslash hack

  • The box model hack

If you do not wish to use the Java implementation of YUI Compressor, a JavaScript port is available at www.phpied.com/cssmin-js/. Many other options for minifying CSS exist, including the following:

  • www.csscompressor.com/

  • www.cleancss.com/

  • www.cssdrive.com/index.php/main/csscompressor/

Because of the widespread use of YUI Compressor, it is the one we recommend. The sheer amount of developers using it and contributing to the community mean it is a very robust and well-tested solution. Whatever you decide, we definitely recommend using a minification tool with your CSS code despite the implications for debugging (you can read more about debugging in Chapter 10) which can be overcome.

Compression

After we've got our files as small as we can get them, there are still some tricks we can employ to get the data transferred from the server to the user even smaller. One of these is compression. Compression of files involves using algorithms for storage of repetitive data, and representing that data just once instead of many times. There are two main kinds of compression: lossless and lossy. Lossy compression means some information is lost in the compression process—JPEG is an example of a lossy compression format. The more we compress a JPEG image, the more artifacts show up and the further we are from the original image. This is acceptable for an image format since what we are trying to convey is not completely lost. However, for CSS files, any artifacts introduced into the data would break our syntax, and render our files unpredictable and useless. Lossless formats, such as zip, tar, and rar files are what we need for data where every character is important.

HTTP 1.1—the protocol used to deliver web pages—supports three compression methods in its specification: gzip (GNU zip), deflate, and compress. The uptake for deflate and compress by browsers and servers has been slower than for gzip. All modern browsers and servers support gzip compression for data and as such, it has become the industry standard technique to compress data over the Internet.[71]

An example process of a request from the browser to the server and the response from the server is shown here:

  • The browser makes a request to the server, and includes headers similar to these:

    GET /index.html HTTP/1.1
    Host: www.domain.com
    Accept-Encoding: gzip
    User-Agent: Firefox/3.6

    These lines in turn, mean the following:

    • The browser is requesting the file /index.html using the HTTP 1.1 protocol.

    • The browser specifies the domain it wants the file from.

    • The browser indicates that it supports gzip encoding.

    • The browser identifies itself as Firefox 3.6.

  • The server locates the file and reads it into memory.

  • If the browser indicated that it supports gzip compression (which it did in this instance) the server compresses the file.

  • The server returns the data, with headers similar to the following:

    HTTP/1.1 200 OK
    Server: Apache
    Content-Type: text/html
    Content-Encoding: gzip
    Content-Length: 12345

    These lines in turn mean the following:

    • The server confirms it is using HTTP 1.1 as the protocol, and sends a 200 status code to indicate everything is okay.

    • The server identifies itself as Apache.

    • The server identifies the contents of the response as HTML.

    • The server informs the browser that the data is compressed using gzip.

    • The server specifies the size of the data it is returning, so that the browser can show a progress bar, and be sure of when it has received all the data.

  • The browser decompresses the data, and reads it.

Implementing gzip compress on the server is easy with the latest web servers, as you'll see in the next few sections.

Apache

mod_deflate is the module Apache uses to compress files. It is included in the Apache 2.0.x source package. With Apache installed, edit the appropriate .conf file and make sure the following line exists:

LoadModule deflate_module modules/mod_deflate.so

Some older browsers have issues with particular types of data being compressed. It is easy to target them, and there are no performance implications in doing so. Netscape 4.x has issues with anything other than HTML files being compressed, so we should add the following:

BrowserMatch ^Mozilla/4 gzip-only-text/html

Specific versions of Netscape 4 have worse problems even than that, so we can disable gzip compression for those entirely:

BrowserMatch ^Mozilla/4.0[678] no-gzip

Although IE has a user-agent that also begins with "Mozilla/4" it has no issues with gzip compression, so we can cancel those commands for that browser:[72]

BrowserMatch MSI[E] !no-gzip !gzip-only-text/html

We don't want to compress images since they have their own compression already. Compressing those files only inflicts greater processing loads on the server and browser needlessly, with no file size advantage. We can stop that with this line:

SetEnvIfNoCase Request_URI 
.(?:gif|jpe?g|png)$ no-gzip dont-vary

Some versions of Adobe Flash have issues with being compressed via gzip, so if you find you are having problems with SWF files, you can amend the line to be as follows:

SetEnvIfNoCase Request_URI 
.(?:gif|jpe?g|png|swf)$ no-gzip dont-vary

Finally, some proxies and caching servers make their own decisions about what to compress and what not to, and may deliver the wrong content with this configuration. The following line tells those servers to enforce the rules we have set:

Header append Vary User-Agent env=!dont-vary

Now any requests for CSS files (as well as other files) will be gzipped for browsers that support it.

Microsoft IIS (Internet Information Services)

Enabling gzip in IIE is even easier.

For IIS 6 (see Figure 8-1), right-click Websites (or just the website you want to enable compression on) and choose Properties. Click the Service tab. You can choose to enable compression for all static files (images, CSS, fonts, JavaScript, SWFs, and so on), all application (dynamic) files, or both.

Enabling HTTP compression in IIS 6

Figure 8-1. Enabling HTTP compression in IIS 6

For IIS 7, gzip compression is enabled by default for static files. For dynamic (application) files, you can enable it with the following command:

appcmd set config -section:urlCompression /doDynamicCompression:true

For versions of IIS previous to 6, file compression was unreliable, and we do not recommend it be enabled in a production environment. For version 6, you can specify the static file types to compress with the two following commands:

cscript adsutil.vbs SET W3SVC/Filters/Compression/Deflate/HcFileExtensions "htm html txt css"
cscript adsutil.vbs SET W3SVC/Filters/Compression/gzip/HcFileExtensions "htm html txt css"

The final argument (in quotes) lists the static files to be compressed in a space-delimited list.

For IIS 7, there is a file called applicationHost.config that uses XML syntax, which typically lives at C:WindowsSystem32inetsrvconfigapplicationHost.config. This file lists the mime types for dynamic and static files that should be compressed. The static files are in a tag called <staticTypes>. Here's some example content for this tag:

<staticTypes>
    <add mimeType="text/*" enabled="true" />
    <add mimeType="message/*" enabled="true" />
    <add mimeType="application/javascript" enabled="true" />
    <add mimeType="*/*" enabled="false" />
  </staticTypes>

This tag supports wildcards in the mime type, so the entry "text/*" will tell IIS 7 to compress CSS (for which the mime type is "text/css").

Almost all modern web servers will support gzip compression. Check the documentation for your web server to find out how to enable it, if it is not enabled by default.

Using an inspection tool like Firebug (or one of many other tools, see Chapter 10), you can inspect the responses and requests taking place, to ensure gzip compression is occurring, and to get an idea of how well your files are being compressed.

Here is an example of the request/response process taking place for a particular site, in this instance: http://stackoverflow.com.

First the request (see Figure 8-2).

Request headers

Figure 8-2. Request headers

Then the response (see Figure 8-3).

Response headers

Figure 8-3. Response headers

You can see from these that our initial Accept-Encoding header stated that we support gzip (as well as deflate). You can then see that the response was encoded with gzip (by examining the Content-Encoding header). This proves that gzip is working on the server. You can also look at the line above this section to see the overall result of the request at a glance (see Figure 8-4).

Basic HTTP request details

Figure 8-4. Basic HTTP request details

We can see from this the exact file that was requested, the HTTP status code returned from the server, the domain the file was requested from, the size of the file, and the amount of time it took to load the file (broken down into smaller parts). We will look at Firebug in a lot more detail in Chapter 10.

Using another add-on for Firefox from Yahoo! called YSlow, you can also see the size of the file before being gzipped (see Figure 8-5):[73]

YSlow showing file size before and after compression

Figure 8-5. YSlow showing file size before and after compression

This shows us that the file was 31.7 KB before being gzipped, and 7.8 KB after. That's a saving of approximately 75 percent! As is clear, this technique is an essential and easy thing to implement to keep our file sizes down.

A more fully featured alternative to YSlow is Web Page Test (www.webpagetest.org/), which was developed by AOL before being open sourced in 2008. It allows you to run tests specifically against all versions of IE, and even run them multiple times and show averages, for more accurate results.

Content Distribution Networks (CDNs) and Domains

Browsers impose a limit for the number of connections they can have simultaneously (usually around 35). This is a sensible thing; if we tried to concurrently request 200 files at exactly the same time, the speed of each connection would drop dramatically. But more than that, they impose a limit on the amount of simultaneous connections they can have to a particular domain. This restriction has loosened recently, and some browsers let you modify this value, but by default:[74]

  • IE 6 and 7 support 2 simultaneous connections per domain.

  • IE 8 supports 6 simultaneous connections per domain.

  • IE 9 supports 2 simultaneous connections per domain (at the time of writing—presumably this limit will increase when IE 9 leaves beta).

  • Firefox 2 supports 2 simultaneous connections per domain.

  • Firefox 3 supports 6 simultaneous connections per domain.

  • Firefox 4 supports 6 simultaneous connections per domain.

  • Safari 3, 4, and 5 support 4 simultaneous connections per domain.

  • Chrome 6, 7, 8 and 9 support 6 simultaneous connections per domain.

  • Opera 9 supports 4 simultaneous connections per domain.

  • Opera 10 supports 8 simultaneous connections per domain.

Most current browsers support at least 4 simultaneous connections per domain. What this means is that if we have an HTML file, a CSS file, a JavaScript file, 4 background images, and 4 inline images, we already have 11 individual items we need to connect to, and at most 9 of them could be queuing behind other connections.

Tip

Browserscope (www.browserscope.org/) is a fantastic resource for comparing browser capabilities such as these.

Also, when sending requests for files, the headers we've shown so far are not the only ones that get sent with our request. Another header that deserves careful attention is the "Cookie" header. Cookies are pieces of information used to store persistent data and session information on the user's machine, so that when browsing from page to page, data can be kept locally and referenced by the server. Expiration dates can be set on a cookie, so they can even persist when the browser is closed and reopened at a later date. Cookies are associated with a domain, and any request for resources from that domain includes the cookie in the request. Read that again—"any request." Where we are requesting dynamic content from the server, the cookies can be vital to the correct functioning of these pages, but cookies also get sent with requests to static files like images or CSS files.

On some sites, these cookies can be of a not insignificant size. The maximum size for an individual cookie is 4096 bytes (4 KB), and a maximum of 20 can be set against a particular domain, resulting in a total cookie size of potentially 80 KB for a given domain, excluding the delimiters required to send them. Although this is an extreme case, if we sent 80 KB of data with every image request for a page, the time to download all those images would increase a great deal.

The solution to these issues is simple. If we break our main/dynamic content and our static content domains apart, two big things happen:

  • Our cookies are no longer sent to our static content. This results in a smaller request, which is great news and has no impact since that data was worthless, anyway.

  • Our connection limit applies to the individual domains instead of just one, doubling our connection limit.

Achieving this does not mean a huge rethinking of our architecture. If we have an initial domain of http://somedomain.com, we can continue to serve our home page and dynamic content from http://somedomain.com and http://www.somedomain.com. We can then create http://assets.somedomain.com and point that to the same location with domain name server (DNS) entries. Nothing needs to change in our folder structure and already we have improvements to our performance. This doubles our connection limit, but we can take it even further than that. A simple rule we can follow lets us be consistent in these subdomains, and yet have lots of them. If we consider the file suffix of the types of files we are referencing, we can use those in our domain names. For example:

  • http://www.somedomain.com and http://somedomain.com for our primary and dynamic content

  • http://gif.somedomain.com for gif images

  • http://jpg.somedomain.com for jpeg images

  • http://png.somedomain.com for png images

  • http://swf.somedomain.comfor swf files

  • http://css.somedomain.com for css files

  • http://js.somedomain.com for js files

If we implemented this consistently, our CSS would end up with rules such as:

input {background: url(http://css.somedomain.com/css/img/shadow-background.gif);}

This completely goes against our attempts to minimize file size. Instead, we should point the domain http://css.somedomain.com directly to our CSS folder, and continue to reference files within this folder relatively. This is a good compromise between minimizing the file size of our CSS files, and having multiple domains. We can reference our CSS with:

<link rel="stylesheet" href="http://css.somedomain.com/style.css" type="text/css" />

and continue to reference our images like so:

input {background: url(img/shadow-background.gif);}

Although the example above may be overkill, and it may not be feasible to implement quite so many subdomains, just one will give worthwhile performance and bandwidth cost improvements. In fact, there is an impact in performance of having multiple domains. Each extra DNS lookup incurs a performance hit, and so the benefits of these must be weighed up against the DNS lookup cost. Up to four extra domains can be considered a reasonable amount.

An alternative to using subdomains is to use a content distribution network (CDN).

As you will see later in this chapter, there are many points between a user's computer and the server they are connecting to; each one having performance implications.

CDNs distribute your static content over many servers and always attempt to respond to the user from the CDN closest to the user's location. This can result in great performance improvements. The difficulties in implementing a CDN vary vastly between the options available, but the benefits of using them are undeniable. Although there are several free CDNs available, for high-traffic websites you need to use a commercial offering to deal with the kind of bandwidth you are concerned with. Some of the better-known commercial CDN offerings are the following:

  • Akamaiwww.akamai.com/

  • Amazon CloudFronthttp://aws.amazon.com/cloudfront/

  • Microsoft Windows Azurewww.microsoft.com/windowsazure/

Whether you choose to serve your static CSS files locally with subdomains or via a CDN, for a high-performance website, these are things that must be considered.

Having Fewer Requests Is More Important than File Size

With a website of a reasonable size, there will not be just one CSS file that you are working on. Working in that manner would be ridiculous, for several reasons:

  • Multiple developers would probably be working on the same file simultaneously, with all the negative issues that accompany this methodology such as conflicts and merging problems.

  • Scanning and reading files of this size is difficult, when you are only concerned with a small portion of them.

  • Serving just one file for a website penalizes users. Downloading one large file on the home page makes that page very slow to render, when many rules or selectors may not be needed except for specific areas of the site.

It makes a lot more sense to section off the CSS and work on little pieces individually. For the sake of example, let's list a few files we might be using:

  • reset.css

  • global.css

  • home.css

  • login.css

The reset.css and global.css files are ones we expect to use on every page, whereas home.css is just for the home page, and login.css is just for the login page. If we left the files like this, our home page code to link to our CSS files would look like this:

<link rel="stylesheet" href="http://css.somedomain.com/reset.css" type="text/css" />
<link rel="stylesheet" href="http://css.somedomain.com/global.css" type="text/css" />
<link rel="stylesheet" href="http://css.somedomain.com/home.css" type="text/css" />

This has a few implications. The most obvious is that our home page HTML is a bit bigger than it needs to be. Next, we have to create three separate connections to fetch each of these files. Quite aside from the maximum connection limits, this has another more extreme impact on performance.

It turns out that file size and connection limits are not the only things that make files slow to load. Let's look again at the section of Firebug that shows us how long the file took to download (see Figure 8-6).

The stages of an HTTP request/response

Figure 8-6. The stages of an HTTP request/response

You can see here that the receiving of data is not the biggest culprit in this particular item's load time. Let's break this into pieces as if we were dealing with http://css.somedomain.com/home.css.

Domain Name Server (DNS) Lookup

DNS is a kind of directory to map domain names to IP addresses. At this point the browser is first trying to find out where css.somedomain.com points, so it contacts a DNS server (which one is specific to the user's network setup), asks that server for the IP address for that domain and receives a message back with the answer. If the server already knows the result, it returns it immediately from its cache; otherwise, it asks other DNS servers further upstream. The nslookup tool (see Figure 8-7) makes it easy for us to see the results of this process.[75]

The nslookup tool

Figure 8-7. The nslookup tool

Fortunately, DNS is very cacheable and typically very fast. Once the user's browser knows the IP address for a particular domain, it (typically) won't ask again in the short term. However, getting the response back from the server the first time does incur a performance hit.[76]

Connecting

At this point, the browser is actually establishing a connection with the server. Since it has the direct IP address, it is reasonable to assume that this process should be very fast, and the browser can connect directly to the remote machine. Unfortunately, this is not the case. In between the user's machine and the server are several routers the packets need to pass through to establish the connection. The traceroute command lets us see evidence of this (see Figure 8-8).

The traceroute command

Figure 8-8. The traceroute command

As you can see, in this particular example there are 11 separate routers between the user's computer and the server. This incurs a significant performance hit and happens for every request to the server.

Sending

At this point the connection is established, and the user's browser is sending data to the server. This data includes the request headers and any cookies stored locally for that particular domain. For CSS files, we have already discussed how best to minimize this delay. In the example shown previously, there is very little data to send, and this incurs virtually no delay.

Waiting

The server has now received the user's request and is processing it. A certain amount of processing will always take place, even if it just involves the remote server reading a file and returning it, or a caching server reading the data from memory. If the file requested is dynamic and requires processing particular to the user (that cannot be cached remotely), the performance hit will be larger.

Receiving

Finally, the data is returned from the server, and the browser receives it. In this instance, the data returned is very small and the network speed very fast, so the delay is negligible.

What we can see from this process is that even when we minimize the data sent and received, there are still other factors that impact performance to a large degree. Every connection to the server incurs an overhead, and often this overhead is greater than the impact of increasing file size by a large amount. The lesson to be learned from this is that even though minimizing file sizes is best practices, and certainly something to strive for, having fewer requests has an even greater effect on performance.[77]

Concatenation

Our initial example shown following is now obviously not the optimal way to form our CSS files:

<link rel="stylesheet" href="http://css.somedomain.com/reset.css" type="text/css" />
<link rel="stylesheet" href="http://css.somedomain.com/global.css" type="text/css" />
<link rel="stylesheet" href="http://css.somedomain.com/home.css" type="text/css" />

Let's assume in this instance that our reset.css file is 2 KB in size, our global.css file is 4 KB in size, and our home.css file is 6 KB in size. Concatenating (joining together) some of these files into one is an obvious way to reduce the amount of connections to the server. Since reset.css and global.css are required on every page, it seems logical that we should join them together into one, and keep home.css separate to stop the user unnecessarily downloading duplicate content. Actually, for these kinds of small sizes, this is not the truth. If we concatenate all three of these files into one, we will achieve the greatest performance for this page. However, when the user browses to our login page, we want to include these files:

<link rel="stylesheet" href="http://css.somedomain.com/reset.css" type="text/css" />
<link rel="stylesheet" href="http://css.somedomain.com/global.css" type="text/css" />
<link rel="stylesheet" href="http://css.somedomain.com/login.css" type="text/css" />

It would seem counterintuitive to concatenate these three files into one—the user would be downloading the exact same 6 KB (reset.css and global.css) as before. This seems like a duplication of effort. But actually, the extra 6 KB download is unlikely to be as big a hit (for most users) as the cost of an extra HTTP request.

How this works on your high-traffic site is dependent on how big the global and specific files are and requires testing to ensure that you get the best results.

We could also consider concatenating all four files together. This is a larger initial hit, but will be cached after the first page, and not requested by the server for other pages that use the same CSS.

To join these files together is a simple process, but is best dealt with in your build process (as described in Chapter 9). This allows us to work with our uncompressed individual files for maximum legibility, while still ensuring that the code that goes into production is as small and efficient as possible.

CSS Sprites

CSS sprites are a great way to improve performance, and to make pages feel snappier and more responsive. Often in our pages, a graphical button may have several states:[78]

  • Normal—The regular state of the button

  • Hover—An image displayed to indicate to users that their mouse is currently over something clickable

  • Clicked—An image displayed to indicate to users that they have successfully interacted with the button

  • Disabled—An image displayed to indicate to users that this button will not react to interaction

To achieve this effect, typically four separate images are used. For the sake of demonstration, let's look at the hover state:

a {
        background: transparent url(img/button.gif) no-repeat 0 0;
        height:40px;
        width:120px;
        display:block;
}

a:hover {
        background: transparent url(img/button-hover.gif) no-repeat 0 0;
}

How browsers choose to deal with the hover image varies from browser to browser. Some may load it immediately into a cache, so that there is no delay to load the image when the user hovers over the anchor. Others may choose to wait until the user hovers over the element before requesting the image from the server. The first example incurs the performance cost of an extra request to the server. The second incurs this cost, but only after load time when the impact is smaller. There is, however, a delay in showing this image to the user giving a worse experience.

There is a solution to this issue. CSS sprites allow us to stack multiple images into one (see Figure 8-9).

An example sprite image

Figure 8-9. An example sprite image

This image includes our regular button, and right beneath it our hover state for the same button. By using background positions, we can reference the same image in two places in our CSS, but effectively crop parts of it out to only display the parts we want. Using this technique, we can amend our CSS like so:

a {
        background: transparent url(img/button.gif) no-repeat 0 0;
        height:40px;
        width:120px;
        display:block;
}

a:hover {
        background-position: 0 40px;
}

Now there is only one HTTP request, and our button reacts instantly to the mouse hovering over it. It is easy to get enthusiastic about this technique and think "Hey! Let's put every single image on our entire site into one huge CSS sprite!" but before you get too excited, close Photoshop and read about why this is a bad idea:

  • CSS sprites are not appropriate for everything. For elements where the size can be unpredictable (that may resize to their content or scale with font-size changes in the browser), the results may be unpredictable too.

  • A large file full of images quickly becomes very difficult to manage, both for designers and your CSS developers.

  • Deciding that a sprite in the middle of your image needs to change size has huge knock-on effects for the rest of the image.

Instead, we recommend that you use CSS sprites anywhere an image may have multiple states, such as the button demonstrated earlier. Try to avoid allowing elements that use sprites to change dimensions in such a way as to show other sprites or crop them in undesirable ways. If this is not possible, make sure to add enough space between each other, so the adjacent images aren't shown if the visible area changes (like when you increase font size or zoom in a page). As always, find the balance between what is good for your users and good for your developers, and make a decision within your organization on how to approach this. An exception to this would be for mobile sites, where the latency can be so large that every extra HTTP request makes a significant difference to page rendering speed.

Data URIs (Uniform Resource Indicators)

Instead of representing an image as an individual file, it is possible to encode images into a big string of data that we can insert straight into our files, like so:

<img src="data:image/png;base64,ABCDEFHIJKLMNOPQRSTUVWXYA" />

...or:

background-image: url("data:image/png;base64,ABCDEFHIJKLMNOPQRSTUVWXYA");

This method has one obvious massive benefit: It saves us that extra HTTP request for each image we include in this way. Where CSS sprites are not appropriate (for example, for elements with unpredictable sizes) they also give you immediately responsive hover states. Unfortunately, it has a lot more disadvantages:

  • Control is lost for the caching of these images at a more granular level than that of the file they are included in

  • The representation of the file actually ends up about a third bigger than the regular image file

  • IE 7 and below do not support data URIs at all

  • IE 8 limits data URIs to 32 KB or less

Due to these restrictions, we cannot recommend data URIs unless they are for use in an environment in which you can accurately predict or control the browsers in use.[79]

Caching

Caching is the act of storing commonly accessed data so that it can be more quickly retrieved to improve performance. Caches potentially exist at several different points:

  • Browser cache—On the client (the browser) and specific to a single user of a machine (although multiple users may use this account)

  • Proxy cache (or Shared Cache)—Cache provided by the Internet service provider (ISP) or third party between the client and the server and shared between many users

  • Gateway cache—Cache implemented by the owner of the web server such as caching servers or CDNs and shared between many users

Caches primarily serve two purposes: to reduce latency and reduce network traffic. Typically these two goals are in line with everyone's best interests. Everyone wants the user to receive timely information and data as quickly as possible. Everyone wants the least data transferred as possible to further decrease latency and save money. As the owner of a high-traffic website, the only caches we have full control over are the gateway caches that we have implemented. However, there are commands and hints we can give to the other caches and have a reasonable expectation that they will be followed. These commands are implemented via the response headers from the server, indicating to the browser and proxy caches in between whether (and for how long) a particular item should be stored locally before being requested again.

How the browser/proxy cache chooses to interpret this information is initially its choice, and there is no guarantee that the headers we use will be implemented properly, or read at all, although most browsers/proxies have now standardized and are fairly consistent in how they deal with this. Additionally, user settings in the browser may override this behavior and it is not uncommon for the cache to be disabled entirely on the client side. Caches may be implemented in the short term much more vigorously, so that the same image appearing in multiple places requires no further requests to the server, and that clicking the Back button gives an immediate response.[80]

When serving content, it is up to the web server to provide appropriate cache control headers. The first method for controlling cache historically was the Expires HTTP header, which can be set to a date, after which the browser re-requests the data from the server. This method has been deprecated for a few reasons: the time may be out of sync between the resources that are serving the data (such as the gateway cache and proxy caches) and it is necessary to update this value automatically at certain intervals. The HTTP 1.1 specification instead has defined a new header called Cache-Control, which stores a series of individual values that let the different caching layers make intelligent decisions about whether to serve the local copy of a file or fetch a new one. This decision is based upon two factors:[81]

  • Freshness—A file can be marked as fresh for a certain period of time, after which it is stale and must be re-requested from the server.

  • Validation—A request can be made that lets the caching mechanism know whether the local stored copy is the most recent without having to request the entire file.

Let's have a quick look at how the caching mechanism typically decides whether or not to fetch a new copy of the file.

  • If there is a header specifying not to cache the file or it is served over a secure (HTTPS) connection, it will not be cached.

  • If the item is considered fresh, it will be cached until a request is made after this ceases to be the case.

  • If an item is not considered fresh, an attempt will be made to validate it. If it is found to still be valid, the cached item will continue to be served. If it is not, a new copy will be requested from the server.

The Cache-Control header is made up of several comma-separated values. Here are some of the more important values, the implications of which should be well understood:

  • max-age—Defines (in seconds) the period of time since the request that the file should be considered fresh.

  • must-revalidate—Under special conditions, HTTP allows caches to serve stale caches of files. This header tells the caching mechanism not to follow that behavior.

  • no-cache—Instructs the caching mechanism never to cache this file.

  • no-store—Instructs the caching mechanism never to keep a copy of this file; a cache can be stored and served from memory, but never written to disk.

  • public—Indicates specifically that an item is cacheable by shared caches, even if there is a secure connection in place.

  • private—Indicates specifically that an item is cacheable, even if there is a secure connection in place—but only in the client-side cache.

  • proxy-revalidate—Under special conditions, HTTP allows caches to serve stale caches of files. This header specifically tells shared caches not to follow that behavior.

  • s-maxage—Defines (in seconds) the period of time since the request that the file should be considered fresh (but only for shared caches).

Here is an example header that tells all agents never to cache an item:

Cache-Control: no-cache, must-revalidate

Here is an example header that tells all agents to cache an item for a year, regardless of whether it is accessed through a secure connection:

Cache-Control: max-age=31536000, public

Validators are what the agent uses to detect whether an item has changed. In our first example, the caching mechanism will check for a new version on every request. In the second, it will wait a year before checking. Validation is the act of making a special request to the server, including validation information, where the server will not return the file if the cached copy is still valid. There are two main validators the caching mechanism will use.

The first validator is the time since the file was last modified, which is returned as a date in a response header called Last-Modified. Rather than making two requests (one to check the validity and another if the local copy is found to be invalid), the agent can make a specific type of request called If-Modified-Since and include the appropriate date with this request.

The second validator was introduced in HTTP 1.1 and is called the ETag. The ETag is built based upon the contents of the file, and is a kind of fingerprint of the file. When making requests to the server based upon stale caches, the agent will send a request header called If-None-Match and include the stored ETag.[82]

In both of these instances, if the content has not changed, a 304 HTTP status code is returned that means Not Modified and instructs the caching mechanism to use the stored representation of the file. If it has changed, the normal full response including the file contents is returned and should replace the cached copy.

Modern web servers return the Last-Modified and ETag headers automatically, and should require no further configuration.

Note

ETags are generated to be unique to the file they are generated for, but also for the server they are served from. Gateway caches or load balancing servers could make this header unpredictable. For this reason, they are often inappropriate for high-traffic websites, and Last-Modified tags should be used instead.

When using a browser, there are methods to force a full refresh and bypass the cache. For example, in Firefox hold down shift and F5 in Windows, or shift-command-R in OS X. This sends an extra request header called pragma with a value of no-cache, telling the server and any shared caches to ignore any caching commands in the request or caching data stored locally, and return up-to-date versions from the server.

What Should We Cache?

CSS files are typically very static and thus very cacheable. What this means is that their content does not change dependent on other factors, such as cookies or user-supplied content. What one person receives as the content of a CSS file is exactly the same as what another receives.[83]

Where this is true, caches are our friend and dramatically improve performance at every level. We should therefore set caches for images, CSS files, and other static files to be very long-lived. Also (typically) there are no security considerations for these kinds of files. This means that regardless of whether these files are being accessed over a secure connection, we should still cache them and serve the same copies to all users.

Our previous example of a long-lived cache is still appropriate in this instance:

Cache-Control: max-age=31536000, public

We could set a longer max-age than a year, but it is unlikely that someone would still be working from this cache in a year's time.

Edge Side caching is often a useful and effective technique, particularly for dynamic CSS. We will mention Edge Side caching in the next chapter.

Best endeavors should be made to keep all CSS and JavaScript in external files, not inline in the page because HTML should have a short cache life, and CSS and JavaScript should have longer-lived caches.

Versioning

When working with cached files with long max-ages, versioning becomes a real problem. As we modify our CSS files, the caching mechanism behaves exactly as we asked it to, meaning that when we want to stop caching files we need a new tactic. The only real solution is to change the name of the file. Consider the following scenario.

Our file home.html has been modified, with substantial changes to the markup. Our main stylesheet style.css has also been modified to accommodate these changes. We have given our stylesheet a long cache expiration to ensure it is not requested when it is not necessary. Suddenly, all our users are complaining that home.html looks broken! What has happened is that the changes to home.html have reached our users, but the changes to style.css have not because that file is still available in their local cache.

The best way to fix this is to version our CSS. Versioning CSS files is much simpler than it sounds. We do this by simply manually modifying the name of the file to include a version number or date.

For example, style.css becomes style.1.0.css. The numbers 1 and 0 represent "major" and "minor" revisions, respectively. We increment the minor number whenever we make a small modification. If it is a dramatic modification, we increment the major number and reset the minor number to 0. So, in this instance we have only made a small change to the CSS, and the file name becomes style.1.1.css. Our new markup references this file. We have a very long cache expiration on the CSS files, but not on the HTML. If the user is fetching the latest version of our markup, they get style.1.1.css, which is not yet cached in their browser because it is a new file. If the user has an older version of our markup, they get style.1.0.css from their cache or as a new request if for some reason that piece of their cache is cleared or corrupted. We could just as easily have named the file style-2010-12-31.css or style-strawberries.css; the important thing is that the file name be unique. We could also have broken out of the cache by including a querystring in the URL, such as style.css?v=1.1, which would force the refresh of the file, but would preclude us from keeping the previous versions and thus our backwards-compatibility.

The benefit of multiple files existing simultaneously is that it ensures the integrity of our system, regardless of which version of home.html is running on the browser of our site visitors. If they have the old version for some reason (it is possible that there could be a problem with a shared cache or that a version is still cached) the appropriate version of CSS will still be requested. If they have the newer, the same is still true.

As part of our build process (see Chapter 9), we could make this automatic.

What About Offline Storage?

HTML5 gives us an offline storage mechanism so that we can serve pages and web applications without any requests to the server. This can be achieved via the manifest attribute in the html tag:

<!DOCTYPE html>
<html manifest="/cache.manifest">
...

The manifest file (which should be returned with the mimetype text/cache-manifest) that we specify contains information about which files should be automatically cached. These files will only be updated when the cache manifest file changes according to our normal caching rules (i.e. via ETags or Last-Modified). An example manifest file is as follows:

CACHE MANIFEST
/css/style.css
/css/img/background.png
/js/main.js

Since this file in effect overrides all of your existing caching mechanisms, when your files change this file also needs to change. Any line in a cache manifest beginning with the hash symbol is treated as a comment, and this is the easiest way to register changes to your files, without having to modify the manifest itself:

CACHE MANIFEST
# Updated 2010/04/01
/css/style.css
/css/img/background.png
/js/main.js

Any time this file is changed, all the files stated will be redownloaded and your existing cache strategies will not take effect. If only one file has changed (as will often be the case), this is a very inefficient manner of managing caching and performance. Because of this and the poor browser support at time of writing, we do not recommend it as a strategy for caching regular content, although it is still a great technique for its originally intended use.

Cache manifests are useful in all kinds of scenarios and have more complex syntax than that listed here, although they are beyond the scope of this book.

Rendering and Parsing

Comprehending how the browser renders our page is vital for understanding how to get the best performance from your pages. The browser reads the HTML first and then requests the items in the HTML from the top first to the bottom last. If the browser finds CSS files linked at the bottom of the page, many browsers will render nothing until everything in the page is loaded and then the CSS files last. Locating all your link tags in the head tag will ensure that those are loaded first, and the browser can then render the page progressively.

Whenever the browser finds an @import rule, it imports the file referenced, and includes the contents before anything following that directive. In some browsers (notably IE) this blocks the rest of the file from being read and from any other CSS files being downloaded. This means the browser has to wait for that file before continuing to download the rest of the assets for the page. This is just one of many reasons to avoid @import rules.

Because JavaScript files are read in a linear fashion, they block further parts of the page from loading until they have been executed. For this reason, placing them as close to the bottom of the page as possible ensures they will not block any of our other files from being downloaded and read in parallel.

When the browser parses our CSS, it reads our selectors from right-to-left, which can be very unintuitive. Although IDs are very efficient, if we follow them with other less specific selectors, like tag names, these selectors will be read first. As soon as the browser fails to find a match for one of its queries, it ignores the rest of the selector and moves on to the next rule. The fastest and most efficient selectors are those with IDs toward the right of the selector, and IDs are so specific that it often means the rest of the selector is unnecessary. This would indicate that the most efficient CSS possible, is based purely on IDs, but to fill our markup with IDs would be (probably) unsemantic and very difficult to manage. As always, a balance must be found between manageable and efficient code.

Suppose that an ID or class also includes a tag name, as in the following:

div#mainContent {...}
img.heroImg {...}

The browser first queries the document for all elements with those IDs or classes and then attempts to further query the tags within that result set. This is rarely necessary and should be avoided if possible.

The universal selector (*) is the least efficient selector you can use:

body * {...}

Although the selector seems it should be a simple query, it returns all the elements first, then checks the next part of the rule (in this instance, that the elements should be inside the body tag) and is a very inefficient method of locating nodes. You should avoid it as much as possible. You can use the universal selector on its own, like this:

* {...}

Some have noted that the performance implications are reduced in this scenario, but there is no way to do a pure test. It would be a "quantum test," i.e., the act of observing would impact the results. We recommend simply avoiding the universal selector unless it is necessary.

CSS3 selectors are more complex and use more resources to query for. As such, you should avoid them if they are unnecessary. IDs are always the fastest way for CSS to locate elements, with the next fastest being class names.

The loading of CSS files itself causes blocking in some browsers, notably Internet Explorer. As we have mentioned before, you might like to consider "lazy loading" print stylesheets after the page has loaded with JavaScript.

Being pragmatic, however, CSS performance is rarely the bottleneck in the performance of your website. You should always strive to follow best practices and keep CSS performance high, but not if it is at a cost of legibility or file size, as those are more important.

Changing Properties via JavaScript

It is often a requirement to modify styles upon interactions with the page, whether that be a simple visibility toggle for menus or something more elaborate. Lines like the following are very common:

$("#elementID").css({
   "height": "40px",
   "width": "40px",
});

In this example, we have located the element with the ID "elementID" and set its height and width to 40 pixels each. Because there is no way to set individual properties simultaneously, this is the same as the following code:

$("#elementID").css("height", "40px");
$("#elementID").css("width", "40px");

What is important to note is that setting properties in this way forces a redraw of the page in the browser, which is expensive in terms of performance. The positioning of everything must be recalculated twice. If we were setting even more properties, it would happen even more times. Instead, we should have added this to our CSS:

#elementID.modified {
   height: 40px;
   width: 40px;
}

And then changed our JavaScript to the following:

$("#elementID").addClass("modified");

Now the browser adds the class and is able to modify the two properties simultaneously and only forces one refresh. Although it feels as if we are maintaining this information in multiple places, actually the JavaScript is now responsible for the control of classes associated with an element, and all the presentational code is kept in the CSS files, as it should be. If we have to make this change in more than one place in our JavaScript, abstracting it to a simple class is obviously good practice.

Animation

Animation is one of the most resource intensive things our pages will do, and can be achieved in many ways (including via CSS). Although we will not go into the minutiae of CSS transforms and transitions, or exactly how to animate elements on your pages, we will point out some things that will have implications for performance. How the browser renders your pages is vital to understand how to get the best performance out of them.

When animating via JavaScript we want our code to be as efficient as possible. Unless we have a class for every step between the beginning and end of the animation, we cannot easily change multiple properties at a time. To animate with JavaScript, we change a CSS property by a certain amount at particular intervals until we have reached the goal value. If we wanted to animate a box from the left to the right, for example, we might modify the "left" property from 10px to 100px, in 10-pixel increments, every 40 milliseconds. Here's a series of good rules to follow:

  • Animate as few properties as possible.

  • Do as little work per iteration as possible.

  • Set the interval period to the highest value that gives a good result. Setting it too low will result in poor performance; setting it too high will result in jerky animations.

Although animating via CSS is only really supported in WebKit at the time of writing, animating in this fashion brings great advantages—the biggest being that these animations are hardware-accelerated. It is possible to write code that feature detects for WebKit animation events and uses them if they are available, falling back to JavaScript if they are not. When animating via CSS, if animating colors, be aware that the browser converts everything to RGBA during the animation. You will see better performance if you provide the colors in this format in the first place.

Hardware Acceleration

Using CSS transitions and transforms will use hardware acceleration in browsers that support them. You can trick the browser into forcing hardware acceleration on an element (and its children) by moving an element into 3D space like so:

... {
   -webkit-transform-style: preserve-3d;
}

Although this does—in certain scenarios—result in increased performance, even on a 2D plane, in other scenarios this can cause problems. Mobile devices simply don't have the processing power of their laptop and desktop counterparts and do not cope well with this technique. Also, elements in 3D planes are processed through a different pipeline to others, and you may see different text rendering or anti-aliasing results. If you have control of your users' environments, this can be a useful thing to do, but it is not a technique we recommend for typical websites.

Summary

This chapter aims to teach you the many factors that affect your pages' performance, and show you some best practices that you can apply to your own CSS for significant performance gains. In many instances one size does not fit all, and testing will be necessary to understand the balance you need to find between these different techniques. This outlay will pay dividends, both in terms of your site visitors' experience (and therefore their goodwill toward your site and your organization) and in terms of your bandwidth costs.

The next chapter concerns itself with dynamic CSS, and will teach you how you can serve different CSS to different users, as well as using preprocessors such as LESS and Sass to make writing CSS quicker, more functional, and more fun.



[61] Google provides a guide to minimizing your payload at http://code.google.com/speed/pagespeed/docs/payload.html. Yahoo! provides its own guide to maximizing performance of your website at http://developer.yahoo.com/performance/rules.html.

[62] A mime type is a response header identifying the type of file that is being served. The mime type for CSS files is "text/css".

[63] Behaviors are implemented via HTML Component (HTC) files, and supported by IE versions 5 and above. They allow you to assign particular behaviors to elements via JavaScript. Since they introduce a dependency on JavaScript and are isolated to only the IE browsers, we do not recommend you use them.

[64] Although most browsers do not treat font names as case-sensitive, some older ones do. Notably, Adobe Flex (www.adobe.com/products/flex/—a framework for developing Adobe Flash and Adobe AIR applications) is known to have issues with incorrectly cased font names.

[65] The color "orange" is also included in the CSS2.1 spec, defined as #ffa500. The color "transparent" is included in the CSS1 spec for background colors, which has no hex color equivalent. CSS2 made this property applicable to border colors, and CSS3 defines it as applicable to any property that accepts a color value.

[66] Interestingly, the color "green" as an absolute is deemed too bright to be described as green, and is instead named "lime"; "green" as a keyword represents #008000, which is much darker than the absolute green.

[67] If for some reason, you want particular comments to remain (perhaps for copyright or licensing purposes) you can use an exclamation mark at the beginning of the comment, like so:

/*! This comment is far too important to be removed: */

[68] As mentioned later in this chapter, when using CSS transitions there is a performance penalty in some browsers for using hex colors rather than RGB colors, so it is particularly important to be aware of this.

[69] It is possible to define the character set within CSS with a command like:

@charset "utf-8";

any CSS file can only contain one @charset statement.

[70] The filter property (proprietary to Internet Explorer) allows you to set various visual effects. To set opacity, the recommended syntax is:

selector {
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=65)";
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=65);
}

The filter property is for versions of IE 8 and lower. The -ms-filter property is for IE 9 and above. YUI Compressor minifies these to their shorthand equivalents, like so:

selector {
-ms-filter:"alpha(opacity=65)";
filter:alpha(opacity=65);
}

[71] You should be aware that when using gzip, the compression rates achieved are greater for smaller character sets since there are individual characters for the compression algorithm to be concerned with. An easy way to improve performance is to aim to use lowercase characters wherever possible, although this is less important than having fewer characters.

[72] Apache 2.0.48 has a bug in mod_setenvif, which is why the regular expression looks a bit weird in this example.

[73] YSlow is a very useful tool for locating exactly what is slowing down your website, and more than that, gives great indications of things you can do to fix them. You can download it at http://developer.yahoo.com/yslow/.

[74] Interestingly, the connections are not just increasing but going up and down through browser revisions.

[75] Every DNS entry has a Time To Live (TTL). This is how long DNS servers should cache the result for. It is often set to a day or more and is why changes to DNS entries can take a while to propagate to all the servers.

[76] Some browsers, such as Firefox, have an internal DNS cache that they refer to. Getting past this cache requires the user to restart the browser or use an extension.

[77] This is a good reason why the @import directive should be used sparingly; every extra request hampers our performance. Media selectors can be used with the link tag to reference individual files or inline in a CSS file to make our selectors more specific. Which ones you should use depends on how many rules you have that need to be this specific. HTTPS connections incur even greater overheads.

[78] Don't rely on hover states for anything functional on your pages. To do so is to create a device dependency; the user may have a disability that precludes him from using a mouse, or could be using a touchscreen interface that has no concept of a hover state.

[79] Sveinbjorn Thordarson provides more details about data URIs, as well as tools to generate them, at www.sveinbjorn.org/dataurls_css.

[80] Content served over HTTPS (secure HTTP connections) should not (and typically will not) be cached locally or at proxies due to security concerns.

[81] It is possible to attempt to control browser caching through meta tags in HTML, but few browsers honor them because the browser's caching mechanism inspects the data and makes caching decisions before the HTML is parsed. We do not recommend this technique.

[82] A fingerprint (or hash) of a file (commonly an MD5 hash) uses an algorithm to present a string. Checking the string against the original file lets us know if the contents of the file have changed. This is commonly used to be sure that a file you download is the intended file and has not become corrupt, but it also works perfectly in this instance to check to see whether a file has changed.

[83] This is not necessarily true of dynamically created CSS files. Read more about this in Chapter 9.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset