Using Font Face Observer to optimise font-loading in a WordPress site

What is Font Face Observer?

In the words of its creator Bram Stein :

Font Face Observer is a small @font-face loader and monitor (3.5KB minified and 1.3KB gzipped) compatible with any webfont service. It will monitor when a webfont is loaded and notify you. It does not limit you in any way in where, when, or how you load your webfonts. Unlike the Web Font Loader Font Face Observer uses scroll events to detect font loads efficiently and with minimum overhead.

Font Face Observer is one of several techniques for optimising font-loading and preventing FOIT (flash of invisible text) which can be created by the browser when there is a delay in loading a font. It works with both self hosted fonts as well as those loaded using services such as Typekit and Google fonts.

What is FOIT?

FOIT, or flash of invisible text, is when a browser hides all text until the font is loaded or a specific time is reached. That can be up to 30 seconds in some versions of iOS Safari. It varies between browsers and is most noticeable on slower connections. FOIT not only increases the liklihood that a user will simply leave the site but gives the user a negative impression that the site is generally lagging. FOIT happens whenever the browser tries to render a font face that is not yet loaded.

Optimising font loading in WordPress

Most web font service require a script or stylesheet link as close to the top of the document head as possible, WordPress provides several ways to load different fonts: A link to a web-font service such may be included in the head directly, or via the wp_head() or wp_enqueue_script() hooks. Fonts can also be loaded via a style tag or from a theme or plugins stylesheet with an @font-face or @import rule. No matter which of these methods is used an @font-face rule is available and can be detected by the FontFaceObserver script.

These font requests have a negative impact on performance. Particularly when there are several different typefaces and font weights. The first thing to do will be to load the fonts at the bottom of the page.

  function mytheme_add_google_fonts() {
      wp_enqueue_style( 'mytheme-google-fonts', 'https://fonts.googleapis.com/css?family=Roboto+Slab:400,700', false );
  }

  add_action( 'get_footer', 'mytheme_add_google_fonts' );

font-family rule

The trick is to provide a fallback system font for each case when a webfont is used. The system font will load right away and then will be replaced by the desired font as soon as it is available. This is done using Javascript Promises and is fairly easy to implement..

First include the fontfaceobserver script. The script can be loaded via npm or your package manager of choice. It can also cloned or downloaded directly into your theme or plugin.

Next you will have to call the script from a js file in your theme or plugin.

You can either load fonts at the same time

  var mainFont = new FontFaceObserver('Open Sans');
  var titleFont = new FontFaceObserver('Lato');

  Promise.all([mainFont.load(), titleFont.load()]).then(function () {
    console.log('All fonts have loaded');
    document.documentElement.className += " fonts-loaded";
  });

Or check them separately

  var mainFont = new FontFaceObserver('Open Sans');
  var titleFont = new FontFaceObserver('Lato');

  mainFont.load().then(function () {
    console.log('Open Sans is available');
    document.documentElement.className += " main-fonts-loaded";
  });

  titleFont.load().then(function () {
    console.log('Lato is available');
    document.documentElement.className += " title-fonts-loaded";
  });

Once the fonts are loaded a class name is added to the html element.

You can also use ES6 module imports and optionally save a token in Local Storage so we can take advantage of the font being cached by the browser.

  const FontFaceObserver = require('fontfaceobserver');
  const titleFont = new FontFaceObserver('Roboto Slab');

  titleFont.load().then(function () {
    console.log('Roboto Slab has loaded.');

    sessionStorage.fontsLoaded = true;
  }).catch(function () {
    sessionStorage.fontsLoaded = false;
  });

  if (sessionStorage.fontsLoaded) {
    var html = document.documentElement;

    html.classList.add('fonts-loaded');
  }

The FontFaceObserver library comes with polyfils for Javascript Promises so if you are otherwise loading those polyfils you can use standalone.fontfaceobserver.js

Now the CSS rules need to be slightly modified in order to charge our fonts when they are loaded.

  h1, h2, h3, h4, h5, h6 {
    line-height: 1.25em;
    margin: 0 0.5rem 0.75rem 0.5rem;
    padding-top: 0.25rem;
    font-weight: 700;
  }

  .fonts-loaded h1,
  .fonts-loaded h2,
  .fonts-loaded h3,
  .fonts-loaded h4,
  .fonts-loaded h5,
  .fonts-loaded h6  {
    font-family: "Roboto Slab", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;
  }

which ultimately results in a default font which loads and waits for the desired font. No FOIT and improved performance.

default font