HTML & CSS
Article

Improving Font Performance with Subsetting and Local Storage

By Luis Vieira

A recent technique that seems to be gaining traction recently, is to use a method called subsetting to reduce the size of web fonts, encode them to base64, and store them in local storage. Web fonts if not used carefully can have a huge performance hit, and can block access to your website’s content.

This technique will allow you to reduce the size of your font files and store them asynchronously in local storage, showing a fallback system font while your fonts are being downloaded. This, along with some features inherent to local storage, allows you to have your fonts cached persistently. This means that they will stay on the client’s machine, persist across sessions, and even across a device restart.

Subsetting Your Fonts

Font subsetting is one of the most important things you can do to improve web font performance. Subsetting is nothing more than removing unused characters from the fonts files. Unused characters are normally characters from languages that you don’t use, or special characters that your website or app may not need but are often embedded in font files. By subsetting, you can potentially get up to a 50% reduction in file size.

You can use Font Squirrel’s web font generator to subset and base64 encode your fonts into one final file (be sure to choose the expert option to access custom subsetting, and select “Base64 encode” in the “CSS” section of the form).

When you’re finished, you’ll have in one stylesheet file all of your web fonts, compressed and ready for later reuse with a single request.

Choosing a Fallback Font

To avoid leaving your users waiting while the browser tries to download the font file, it’s good practice to show the user a fallback system font. This allows immediate access to the content (that’s what they’re coming for after all).

A font loading synchronously would leave the text blank while the browser waits for the font file and the user will be unable to read the content while waiting for the file download to complete.

With an asynchronous load and with a good choice of a fallback font, the user will see the text displayed in the fallback font immediately, and the font would shift to your chosen web font when the file is downloaded.

Your fallback font can be styled to make this transition softer, reducing the reflow of content. Because the user has access to your content without delay, this is an immediate improvement to the perceived performance of your site or app.

To discover the system fonts available for different OSes you can check out the following sources:

  • CSS Font Stack – A complete collection of web safe CSS font stacks for Mac and Windows.
  • iOS Fonts – Lists every font for every iOS version.

On Android, it’s more difficult to say which are the system fonts because of the number of forks and different brands using it. However, the most common fonts on Android are: Droid Serif, Droid Sans, Droid Mono, and Roboto.

Using Local Storage to Save Web Fonts

First let’s add a class to the <html> DOM node that will hold the fallback font styles. Using JavaScript, this will later be replaced with a class that has the styles of the loaded font. We will also save a path pointing to the font file to a variable for later reuse.

document.documentElement.className = 'fallback';
var css_href = '../path/fonts.css';

Next we need to check for local storage support by trying to set and get an item into local storage. Some browsers can’t store anything in private mode but window.localStorage will still return a storage object. We need this extra request to make sure that our script will work properly:

var localStorageSupported = function() {
  try {
    localStorage.setItem('test', 'test');
    localStorage.removeItem('test');
      return true;
    } catch(e) {
      return false;
    }
}

If the browser passes the localStorageSupported test and our font file is already stored, we can get the file and add it to the page header inside a style tag using the injectRawStyle() function. If the browser doesn’t pass the test, we call the injectFontsStylesheet() function on the onLoad event, so that we don’t block the ui thread:

if (localStorageSupported() && localStorage.webFonts) {
  injectRawStyle(localStorage.webFonts);
} else {
  window.onload = function() {
    injectFontsStylesheet();
  } 
}

The injectFontsStylesheet() function makes an xhr request to get the font file content, injects it into the header with the help of the injectRawStyle function and saves it to local storage:

function injectFontsStylesheet() {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', css_href, true);
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      injectRawStyle(xhr.responseText);
      localStorage.webFonts = xhr.responseText;
    }
    xhr.send();
  }
}

This is the function that creates the style tag in the head of the document and gets its content through the text parameter. It’s also at this stage that we replace the fallback class with the font class that has the web font styles:

function injectRawStyle(text) {
  var style = document.createElement('style');
  style.innerHTML = text;
  document.getElementsByTagName('head')[0].appendChild(style);
  document.documentElement.className = 'webFont';
}

Now we need the actual styles for the fallback and the web font. You can test these styles by refreshing your browser and watching the reflow of content. The goal is to try to match the fallback font styles to the real styles as much as possible, so that the perception of change becomes virtually unnoticeable.

.fallback {
  font-family: Verdana, sans-serif;
  line-height: 1.58em;
  letter-spacing: 0px;
  font-size: 9px;
} 

.webFont {
  font-family: 'Proxima-Nova', sans-serif;
  line-height: 1.3em;
  letter-spacing: 2px;
  font-size: 13px;
}

Demo and Wrap-Up

If you want to take a look at the code, you can view the JavaScript panel in the CodePen below.

See the Pen 469f4667e96cc9e89208afb7e3bfbfb2 by SitePoint (@SitePoint) on CodePen.

Note that the demo is using an external CodePen as the source CSS, as outlined on the CodePen blog.

With this solution, you’re now able to store your fonts asynchronously into local storage, while providing a good and reliable fallback font.

You can keep experimenting with this technique by trying to improve the appearance of the fallback font to match your chosen web font, or you can try to repurpose this script for other assets such as CSS or JavaScript files.

Comments
MattDiMu

I understand the need for subsetting, but not for base64-encoding & saving it in the local storage. I tried it with OpenSans on fontsquirrel.com and got a 58kB CSS file containing woff & woff2, whereas the original woff2 file was only 19kB (25kB for woff).

Why would i transfer so much more data? Why not simply use the out-of-the-box browser cache for caching the font files? And what about older browsers like IE, who needs eot-file-format? Shall i load & localStorage them too on every browser, even though they don't need it?

luisvieira

Hi by saving the file to local storage that file would get cached persistently in the client, browsers native cache get's flushed frequently, on mobile devices as caches are smaller this may even happen under the same visit. To get a smaller font file you could however make a script that detects the supported font format and create a smaller base64 encoded css file for each font format.

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in Front-end, once a week, for free.