Optimizing your webpage — Font Loading

This article is part of my series on how to optimize a webpage for 10k size.

The behavior of web font loading in browsers is still pretty inconsistent especially once it comes to slow loading. There are plans to introduce a font-display property to let the developer control that behavior, but currently the browser support for it in 2016 is still pretty non-existing.

The issue with webfonts is: what does the browser render, while the font is still loading in the browser? One bad behavior (that some browsers still use) is not showing any visible text while the font still loads. That means even though HTML and critical CSS is there, the user won’t see anything on the page, because the webfont is still missing. Also most of the browsers today wait at least 3 seconds, then fall back to a fallback font, if the font hasn’t finished loading and then swap the true font in once it finished loading. This is also what font-display: block in a @font-face will do, once support is there. So I tried to create an improved font loading.

One possible solution (e.g. The Guardian uses it) is to load fonts via AJAX on the first visit and put them into the Local Storage once they loaded. In any following visit the font is in Local Storage and will be used, but the first visit will just fall back to a system font. This is a good solution for e.g. news pages, that have a high rate of returning visitors. This blog has around 65% new visitors every day, meaning most of the visitors will never see the webfont I wanted them to see, so I could basically get rid of it.

So I started using Font Face Observer, which allows you to load fonts via JavaScript and listening on finished loading events. In the CSS I just used font-family: sans-serif so the user will see a system font while loading. Then I trigger loading of the webfont via Font Face Observer asynchronous. Once loading of the font finished, I set set font-loaded class on the body, which triggers the CSS rule, that has the correct font-face set, i.e. font-family: Roboto in my case. This behavior, where you show a fallback font and swap it once the webfont is loaded (no matter how long it needed), can be achieved with font-display: swap.

This method had two issues for me. If a font is already in cache (e.g. with Service Worker or regular browser cache), you could see the fallback font very shortly before it is replaced with the webfont from cache. This caused an ugly flickering of text on all pages, once you have the font in cache. The second problem was, that I didn’t want fonts to swap in anymore, if they need too long to load. In that case the user might already be in the middle of the article, the font will be swapped in, which causes the browser to render with the new font and all content will jump, due to new font metrics. As I explained in the main article of this series I consider jumping content as one of the main sins in web design.

I first got rid of the second problem. Once the font face finishes loading, I only swap in this font, if the DOMContentLoaded has happened in the last 2 seconds. If the font needed longer to load, I just ignore it for this page view, though the loading still has caused the font to be cached by the browser now. That way I prevent jumping content, on slow connections where the font might take some seconds to load and don’t interrupt the users interaction flow anymore.

To prevent the flickering on every page, once the font is in cache, I set a class on the body with an inline script tag directly during HTML parsing, which will set display: none to the body. This class will be removed once the font is loaded, but no later than 400ms after loading it. If the font is now cached it will load most likely in under 400ms and the body will directly show with the correct font without flickering. You will only see a font-face swap if the font loads longer than 400ms but within 2 seconds. This behaviour is also what font-display: fallback will do, except that it won’t hide the complete body, but just renders the text invisible before switching to the fallback font.

I played around a lot with the values and figured out, I find the loading behavior the most pleasant with 400ms and 2s thresholds. The font-display proposal suggests values of 100ms before showing the fallback font and swapping times up to 3 seconds, but they also use a different way to measure these times, then I do.

To see the code, you can check the fontLoading.js of my blog, which triggers the loading and swapping, the script tag in index.hbs which hides the content to prevent the flickering text, usually called flash of unstyled text (FOUT).

The font-display proposal suggests two more values: auto should let the user-agent decide, what method it wants to use for font loading. optional should use the font if it loads very fast (e.g. because it is loaded from cache) or use the fallback font after that short limit and never swap in the loaded font.