Web font loading in the modern web

Web font loading in the modern web is confusing. Ever since custom web fonts have become widely available the process of loading them have been a topic of debate.

The following outlines what I use and feel is a safe approach to web font loading on production sites. Due in part, because of the non-blocking asynchronous font loading and safe fall back called the Font Observer technique.

All these new beautiful fonts being loaded into websites cause slowed loading due to file size. Rendering undesirable results like FOUT/FOIT. Boston based development gurus, Filament Group, Inc. ran a benchmark on different font loading techniques and found the Font Face Observer technique rendered the first paint the fastest. Resulting in minimized FOUT.

Sounds good. How’s this all done?

The JavaScript checks for any custom fonts being loaded in the HTML/CSS files. If any are found, it’ll add an event listener to check if the font has completed or failed it’s download. A class is then added to the HTML tag if the font has successfully loaded. In the case of a failed download, the native font stack we define in the html style tag will be applied by default.
Let’s get started.

1. Find a custom web font

Find a custom web font that you want to use on your website. Most common fonts are from Google Fonts, or Adobe’s Typekit although any other font host as well as locally hosted fonts should work.

<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">

2. Download FontFaceObserver.js

Download FontFaceObserver.js and place the code in the footer.

<script src="/js/fontfaceobserver.js"></script>

3. Load your custom font(s)

The following code says: If the cookie fonts_loaded isn’t set to true, load the font, set a cookie fonts_loaded and add a class to HTML tag. If there already is a cookie set from a returning user, do nothing. We don’t want to load the font every time a user visits a subsequent page.

Single Font

<script>
// Single Font
var primaryFont = new FontFaceObserver('Open Sans');
if (document.cookie.replace(/(?:(?:^|.*;\s*)fonts_loaded\s*\=\s*([^;]*).*$)|^.*$/, "$1") !== "true") {
  primaryFont.load().then(function () {
    document.documentElement.classList.add('fonts-loaded');
    document.cookie = "fonts_loaded=true; expires=Fri, 31 Dec 9999 23:59:59 GMT";
  }).catch(function() {
    console.log('Fonts failed to load.');
  });
}
</script>

Multiple Fonts

The following code assumes a primary font and secondary font are being used. If the primary font fails to load it will not load the secondary. That’s where the fall back to a native font stack will come in.

<script>
// Multiple Fonts
var primaryFont = new FontFaceObserver('Open Sans');
var secondaryFont = new FontFaceObserver('Montserrat');
if (document.cookie.replace(/(?:(?:^|.*;\s*)fonts_loaded\s*\=\s*([^;]*).*$)|^.*$/, "$1") !== "true") {
  primaryFont.load().then(function () {
    document.documentElement.classList.add('fonts-loaded');
    document.cookie = "fonts_loaded=true; expires=Fri, 31 Dec 9999 23:59:59 GMT";
    secondaryFont.load().then(function () {
    }).catch(function() {
      console.log('Fonts failed to load.');
    });
  });
}
</script>

4. Add font stacks with class

Once the font is loaded the previous JavaScript snippet will add a class to the HTML tag or if it’s a returning user then read from a cookie that’ll add the class.

html {
  font-family: Arial, sans-serif;
}
html.fonts-loaded {
  font-family: 'Open Sans', Arial, sans-serif;
}

5. Use PHP to determine if the cookie is set

Checking the cookie will determine if the font has already been downloaded. If it is, the JavaScript is bypassed and the font stack is fonts-loaded applied from the CSS.

<html<?php echo ($_COOKIE['fonts_loaded']) ? ' class="fonts-loaded"' : '' ?>>

Conclusion

Non-blocking asynchronously font loading with a safe fall back for when fonts won’t load.

All together

Here’s what it should look like all together.

<!doctype html>
<html<?php echo ($_COOKIE['fonts_loaded']) ? ' class="fonts-loaded"' : '' ?>>
<head>
  <meta charset="uft-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
  <title>Modern web font loading</title>
  <!-- CSS -->
  <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
  <style>
    html {
      font-family: Arial, sans-serif;
    }
    html.fonts-loaded {
      font-family: 'Open Sans', Arial, sans-serif;
    }
  </style>
</head>
<body>
  <h1>Modern web font loading</h1>
  <p>Learn more about modern web font loading at <a href="https://jstn.ly/web-font-loading">https://jstn.ly/web-font-loading</a></p>
  <!-- JavaScript-->
  <script src="https://cdn.jsdelivr.net/npm/fontfaceobserver@2.0.13/fontfaceobserver.standalone.min.js"></script>
  <script>
  var primaryFont = new FontFaceObserver('Open Sans');
  if (document.cookie.replace(/(?:(?:^|.*;\s*)fonts_loaded\s*\=\s*([^;]*).*$)|^.*$/, "$1") !== "true") {
    primaryFont.load().then(function () {
      document.documentElement.classList.add('fonts-loaded');
      document.cookie = "fonts_loaded=true; expires=Fri, 31 Dec 9999 23:59:59 GMT";
    }).catch(function() {
      console.log('Fonts failed to load.');
    });
  }
  </script>
</body>
</html>

Justin Mathew

A front end web developer specializing in HTML/CSS, JS, PHP & MySQL with over 10 years experience in the web development field.