June 13, 2017

Font loading strategy: The acceptable FOIT

Custom web fonts have been around for almost 10 years now, yet we still don’t have an optimal way to implement them on our websites. The current font rendering behaviour across browsers is poor and inconsistent, and we as web developers are left with a bunch of workarounds if we want to fix it. This involves the use of javascript in order to control how text should behave while the fonts are loading. If you are completely new to the subject of font loading strategies, I highly recommend reading this comprehensive guide by Zach Leatherman on the different techniques one might use in order to get around the famous “flash of invisible text” (FOIT), while minimising the “flash of unstyled text” (FOUT). I won’t repeat the different approaches that Zach documents in his post. Instead, I’d like to suggest a different strategy that often isn’t considered, and try and convince you why this may be the better strategy for most projects.

A Quick Primer on FOIT and FOUT

If you want to optimise how your fonts behave while loading, you’ll have to get familiar with these two acronyms. If you’re reading this you probably already are, but in case you aren’t here’s a quick recap.

the “flash of invisible text” and the “flash of unstyled text” describes the two main ways a browser can handle the time between the page loads and the fonts load. The fact is that web fonts are relatively large in file-size and the rest of the page is very likely to be downloaded and ready before the font has finished downloading. So we need to decide what to do with the text on the page while we wait for the fonts. We basically have two options. We can either hide the text until the fonts are ready (FOIT), or we can show a fallback font first and then swap it with the web font as soon as it has finished downloading (FOUT).

At the moment browsers approach default font rendering differently. If you simply load your web font in an @font-face block and style some text with it, you’re essentially letting the browser decide how to handle this gap between the page load and the font load. Chrome, Opera and Firefox will hide your text for up to 3 seconds before falling back on a system font. They will then swap it with the web font as soon as it is ready. Internet Explorer will show a fallback font immediately on load instead of hiding the text, and then swap the web font in when it is ready. Lastly, Safari will hide your text for as long as it takes to download the font — potentially forever. So if you don’t do the extra work, you will get FOIT, FOUT or a combination of both, depending on what browser you’re using.

Example of what FOIT and FOUT look like in action.

Font loading essentially comes down to two parameters: “font block timeout” and “font swap timeout”. The real goal is to strike a balance between the two.

  • Font block timeout: For how long do we hide the text before displaying a fallback font?
  • Font swap timeout: For how long do we allow the browser to swap the font after the fallback font has been displayed?

In my opinion, this is a superior way to talk about font loading, because it is more clear that both concepts can be in effect on the same page load. If we break down these two parameters, the default browser behaviour looks like this:

Browser Block timeout Swap timeout
Chrome 35+ 3 seconds Infinite
Opera 3 seconds Infinite
Firefox 3 seconds Infinite
Explorer 0 seconds Infinite
Safari Infinite N/A

Now, once we know what parameters we should be thinking about, we can try and decide what timeout periods results in the best user experience for a specific project. In other words: We can make decisions on how we want to handle the time before the fonts have loaded, and what to do when the fonts have finished downloading.

Performance vs design

Picking a strategy is essentially about finding a balance between performance and design. This is where different strategies may be correct on different projects. At the moment I think we’re trying to find a golden go-to solution, which may not exist. Too many factors play into the decision. What fonts are being used? how big are they? How important is the design to the user experience? How big is the budget? and so on.

The trend that we’re currently seeing among performance-minded developers, is to emulate the behaviour from Explorer across all browsers. The belief here is that “Content is king” and should be served to the user as fast as possible. If that means showing a fallback font first, and then swap the fonts when they’re ready, so be it. Even if it is causing a reflow on the page. We’re essentially choosing a 0 second block period, and an infinite swap period.

Designers, on the other hand, might not find this experience optimal, as we are serving the user a “flash” of a design that isn’t how we intended it. And then after the page looks like it is ready, we reflow the page with the intended font. In some cases, this can cause the user to lose their place for a second, as they may already have begun consuming the content.

This is why I think the correct solution depends on the project’s priorities between performance and design. If the design isn’t critical to the experience, then it probably isn’t the best idea to allow for up to 3 seconds of invisible text. 3 seconds is probably too long for any project as that is a long time to block the user from the content. However, I don’t always think it is correct to show a fallback immediately either. Because in a lot of cases this will result in a quick FOUT on computers with fast connections too, which just doesn’t look great. In some cases, this will even happen on every single page load, which really shouldn’t be the case, as the web font should already be loaded into memory after the initial load.

But perhaps the invisible text and the unstyled text isn’t the problem at all. Perhaps the real problem only happens when the “flash” is longer than a flash.

 

When the flash is longer than a flash

I don’t like the use of the word “flash” in those abbreviations because, in my opinion, it’s only an issue when the wait time is longer than what I consider to be a flash. If the “flash” was guaranteed to be less than 300 ms, I don’t think we would be discussing this issue at all. And if the flash was guaranteed to be that short, I’d even argue that a flash of invisible text would be better than a flash of unstyled text 99% of the time. But since we can’t know how long it will take to download the fonts, we’ll have to decide what to do in cases where the font takes too long, without compromising the experience when they don’t. And this is why it’s useful to talk about “font block timeout” and “font swap timeout” as variables, as this allows us to define how long ‘too long’ is.

How long is an acceptable flash?

If we look at Chrome’s current block timeout, an acceptable flash is up to 3 seconds. I’d argue that an acceptable flash is much shorter than that, but that the exact threshold is project dependant. I know that we don’t consider a website fast enough if it takes 3 seconds to load. So perhaps we should consider our performance budget when deciding what the best thresholds for font loading is. Another important thing to consider is that not all fonts are equally important and might need different timeouts. For example, we might want to prioritise the regular version of our primary font, while only loading the italic and bold versions in the cases where the regular font has been loaded within a specific timeframe. For most projects, I would argue that an acceptable “flash of invisible text” lies somewhere within 100 and 400 ms. If it takes longer than that to load the font, I think the user is better off with a fallback font, so that they can start consuming the content. I admit that these numbers are subjective and you’ll probably have to decide for your self what you think is correct for your projects. But wouldn’t it be great if we could control these two parameters easily in the @font-face block? Well yes, but we can’t yet. So we have to rely on javascript for now.

FontFaceObserver is a javascript plugin that allows us to check if the font is ready within a specific time-frame. We can then decide what we want to do in cases where it isn’t ready within that time-frame, and in cases where it is. This is also the library that Zach uses in almost all of his examples in his guide. But there are a few differences in how he uses it and how I use it, as most of his examples are taking a FOUT-first approach, and then optimising it to be as short as possible. I believe that a short FOUT is worse than a short FOIT, but that a long FOIT is worse than anything else, as it leaves the page completely unusable. So what I’m suggesting is allowing for an acceptable flash of invisible text first, and potentially eliminating the unstyled text completely by not allowing the font to be swapped once rendered to the page.

The Acceptable FOIT

The acceptable FOIT approach that I am suggesting here considers a short flash of invisible text, a good user experience. But anything longer than what we choose as an acceptable “flash” should force the browser to fall back to a system font. Once we show the system font, that’s it. We don’t swap the font after the text has been rendered, as that can cause a disturbing reflow of the page, which isn’t a good user experience. We still allow the browser to load the font into memory though, so that repeat views are almost guaranteed to use the intended web font, avoiding both FOIT and FOUT. This strategy starts by assuming that the custom web font will be available in time, but bails out if it isn’t available within a set timeout (300 ms in my case).

This will serve the custom font on the first page to users with a fast internet connection, but a fallback font to users with a slow connection. The font can then load in the background while they consume the content of the first page, so that it is ready on the next page view. If performance is truly a priority, I think this approach is much better than any FOUT-approach. So let’s see how we might set this up.

First, we need to make sure that we only apply the web fonts when a specific class is set on the <html> element. I use .font-x-available as a conditional class. If you have a single instance in your CSS, where the font-family is set without the conditional class, you will get default browser behaviour on those elements. So keep that in mind.

.font-1-available body {
	font-family: 'MyFont', Sans-Serif;
}

We then assume that the font will be available, in order to get the browser to start looking for the custom font immediately. This is done by adding the conditional classes to the <html> element directly.

<html class="font-1-available">

We then use FontFaceObserver to check if the font is available within a specific time-frame. If it isn’t available, we remove the conditional class from the <html> element, which will cause the browser to show the fallback font. We then set up another timeout to allow the browser to keep loading the font, even though we won’t use it on this page-view. This will allow the browser to store the font in memory so that it will be fetched within the 300 ms on the following pages.

Place this script in the <head> tag of your page, so that it runs as soon as possible.

<script>
// FontFaceObserver plugin GOES HERE!!! 

// If font is available within 300 ms then we're all good.
var font1 = new FontFaceObserver('MyFont');
font1.load(null, 300).then(function () {
  // MyFont is available, let the font-class stay.
  console.log('MyFont is available');
}, 
// Else use fallback font for the rest of the page load.
function () {
  // MyFont is not available
  console.log('MyFont is not available');
  // remove font-class
  document.documentElement.classList.remove('font-1-available');
});

// keep loading the font for later pageviews.
font1.load(null, 3000).then(function () {
	console.log('MyFont is finally available.');
});
</script>

Pros

  • We can decide exactly how long an acceptable flash of invisible text is, and act accordingly.
  • The user will never experience a reflow in the text while reading, as we never swap fonts after the initial render.
  • Users with fast connections will never see the fallback font, and won’t see any artefacts from font-swapping techniques, as the font will never swap.
  • If javascript is disabled, the user will get the default browser behaviour. (I’ve put this as a con as well).

Cons

  • There’s a chance that the first page-load will use the fallback font a bit more often, depending on the font block timeout you set. So we have to be OK with that.
  • You’re forcing the browser to load fonts that might not be used on that particular page. But that is the trade-off to ensure that fonts are available on the next page load.
  • If javascript is disabled, the user will get the default browser behaviour. (I’ve put this as a pro as well).
  • IE will still show the fallback immediately. To get around this, we would have to explicitly hide all text with opacity: 0, until the font has loaded. This could be done but can be an issue in cases where javascript is disabled. So I’d recommend just letting IE be IE in this case.

Shouldn’t we be able to handle this in CSS?

The answer is yes. It would definitely be awesome to be able to handle this is CSS. And the future does look a little brighter, though not perfect. There’s a new property coming to the @font-face rule called “font-display” that allow us to choose between FOIT, FOUT and some in-betweens. It will look like this:

//MyFont
@font-face {
    font-family: 'MyFont';
	src:
        local('MyFont'), // Full name
        local('MyFont'), // PostScript name
        url('fonts/MyFont/MyFont.woff2') format('woff2'),
        url('fonts/MyFont/MyFont.woff') format('woff');
    font-weight: 400;
    font-style: normal;
	font-display: swap; // Basically FOUT
}

The options include

  • auto: Let the browser decide.
  • block: Forces the 3-second block that we’re currently seeing in Chrome, Opera and Firefox. (FOIT)
  • swap: Show fallback immediately, but swap as soon as the web font is ready (FOUT)
  • fallback: The font will be invisible for a very short amount of time (probably 100 ms), then show fallback if the web font isn’t ready and swap when it is.
  • Optional: Same as *fallback*, except it won’t swap the font when it is ready. Additionally, we let the browser decide if it should even load the web font at all on slow connections.

As you can see this might make it easier to implement some of the techniques that we currently need javascript to achieve. While “optional” is probably the closest option to what I am suggesting here, we still won’t have the control that we need. I think we need to be able to control the block period individually for each @font-face declaration and decide specifically if we want that font to swap or not after that threshold has passed. Until we get this granular control, we might still need to resolve to javascript. I personally think they can skip the above property, and instead have two new properties called “block-timeout” and “swap-timeout”. Then I could do this:

//MyFont
@font-face {
    font-family: 'MyFont';
	src:
        // local('MyFont'), // Full name
        // local('MyFont'), // PostScript name
        url('fonts/MyFont/MyFont.woff2') format('woff2'),
        url('fonts/MyFont/MyFont.woff') format('woff');
    font-weight: 400;
    font-style: normal;
	block-timeout: .3; //300 ms
	swap-timeout: 0; // Never swap
}

But once the font-display property is implemented, I’ll probably rely on “optional” in a lot of cases, and then use the FontFaceObserver technique on projects where I need more granular control.