Several years ago the consensus on font loading in the community was that, as a website loads, all fonts should be hidden until the correct resources have been downloaded. Many designers and developers argued that the default font loading method called the “Flash of Unstyled Text”, or FOUT, was an annoyance to users. This is when the fallback web font, say Georgia, is shown on screen first then replaced by a custom font when it loaded. They argued that it would make for a more cohesive browsing experience if users simply waited for everything to download instead of experiencing this flash from one typeface to another.
But today this is not the case.
In fact, this practice of letting a browser hide all the text that should be styled with a custom font is now called the ‘Flash of Invisible Text’, or FOIT, and is often regarded as the worst of all possible options. Scott Jehl has argued that this is a pretty bad idea for both performance and usability:
The FOIT tends to be most problematic in browsers like iOS Safari, which hides text for up to 30 seconds before giving up and rendering it with a default font, but it can also be seen in browsers with shorter hiding durations like Chrome, Firefox, and Opera as well.
Likewise, the Typekit help page on the matter states that “the FOUT approach makes for more immediately usable pages, particularly on slower network connections.” So as designers and developers we have to make a decision between FOUT or FOIT.
Flash of Invisible Text
- Font starts downloading
- Text is invisible whilst web font is requested
- Time passes very slowly
- Web font loads
- Text with the webfont appears
Flash of Unstyled Text
- Font starts downloading
- Immediately show text with
font-family
fallback - Web font loads
- Text with fallback font is replaced with the web font
The difference between these two approaches is often startling. Scott mentions that the FOIT approach caused the text on the Filament Group website to be visible in 2.7s on a 3G connection whereas the FOUT approach made the text visible in 0.6s. If we want to make our interfaces feel as fast as possible then we need to use the FOUT method; user experience and network performance is definitely the main priority with this technique.
Problems with the FOUT approach
This approach certainly has several disadvantages. For example, when the switch between fallback and fancy font occurs there can be an awful lot of juddering and jankiness due to differences in font-weight and x-height. Consequently a company may only want to communicate in a certain typeface for branding reasons but with this FOUT technique that’s obviously impossible.
Sometimes these disadvantages can be mitigated. Take a look for instance at Bram Stein’s website where the slight flash of unstyled text is instantaneous and the format of the page stays almost exactly the same before and after the loading has completed. Also, if we want to use a display typeface in which we can’t find a suitable fallback font then perhaps we can use SVG to display that text instead.
Walking through the FOUT technique
I’ve been experimenting with the Web Font Loader lately which gives developers a little more control as to how fonts are treated throughout the FOUT. First, we need to embed the Web Font Loader code into our markup:
(function(d) { var wf = d.createElement('script'), s = d.scripts[0]; wf.src = 'https://ajax.googleapis.com/ajax/libs/webfont/1.5.18/webfont.js'; s.parentNode.insertBefore(wf, s); })(document);
This asynchronously loads the script onto the page, so you can add it just before the body
ends, or in the head
and the rest of the resources won’t be blocked. This technique is useful for the IE9 support, but if that’s not important for our project then we can use this method instead:
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.5.18/webfont.js" async></script>
Once we’ve got the script we can then add our fonts. In this side project I was experimenting with fonts that are served with @font-face
from the Typonine foundry which we add as a link
in the head
of our markup:
<link rel="preconnect" href="https://fonts.typonine.com/" crossorigin>
preconnect
is useful here because it will automatically make the network handshake for us; before we request the fonts from our script we already have all the information the browser needs to fetch those assets which will make that process just a little bit quicker. (Thanks to Ilya Grigorik for suggesting that we use the crossorigin
attribute here as well.)
Now we can begin to check whether these fonts have loaded onto our page by using the WebFontConfig
object:
WebFontConfig = { custom: { families: [ 'Nocturno Display Medium 3', 'Nocturno Book 2', 'Nocturno Regular Italic 24', 'Nocturno Regular 26', 'Nocturno Regular 25' ], urls: [ 'https://fonts.typonine.com/WF-000000-001254.css', 'https://fonts.typonine.com/WF-000000-001255.css' ] }, timeout: 2000 };
The custom
object lets the Web Font Loader know that we want to load fonts from an external stylesheet, but with this loader we can use fonts from Typekit, Google, Fontdeck, or Fonts.com if we want to. With the families
array we’re identifying all of the font-family names that we would otherwise use directly in the CSS.
You might notice that I’ve set a timeout
of 2 seconds. That’s a completely subjective figure, but I think that’s plenty of time to make a request to the network—any longer than that and the user is probably on a poor connection and all they want is the content anyway.
Once the loader has finished with loading those fonts it will add these classes to the html
element:
<html class="wf-nocturnoregularitalic24-n4-active wf-nocturnoregular26-n4-active wf-nocturnoregular25-n4-active wf-nocturnodisplaymedium3-n4-active wf-nocturnobook2-n4-active wf-active"> <!-- markup for the website goes here --> </html>
These classes are the hooks we need to style the page depending on whether the fonts have loaded or not. There are more classes that can be added to help us however:
loading
: triggered when all fonts have been requested.active
: triggered when the fonts have rendered.inactive
: triggered when the browser does not support linked fonts or if none of the fonts could be loaded.fontloading
: triggered once for each font that’s loaded.fontactive
: triggered once for each font that renders.fontinactive
: triggered if the font can’t be loaded.
Over in our stylesheet, we can now set the right family with the .wf-active
class:
$fallback: Georgia, serif; h1, .h1 { font-family: $fallback; .wf-active & { font-family: "Nocturno Display Medium 3"; } }
So now our users will immediately see the fonts contained in the $fallback
variable but once our .wf-active
class is added by the Web Font Loader then those fonts will be switched out for the fancy web font. We’re now taking a progressive enhancement approach to font loading.
Avoiding the jank
One problem I noticed was that once these fonts had loaded then the number of words that fit onto a line changed and certain elements were too wide or too small because of these new additions to the page.
To fix this we can always set a different font-size
and line-height
combination to make this a smoother experience:
h1, .h1 { font-size: 30px; line-height: 35px; font-family: Georgia, serif; .wf-active & { font-size: 26px; line-height: 30px; font-family: "Nocturno Display Medium 3"; } }
Minimising the flash of unstyled text
After the first page load, The Filament Group recommends setting a cookie so that we don’t have that flash of unstyled text whilst the .wf-active
class is applied to the markup each time. But in my experiment I was just using plain, static markup so the cookie option wasn’t really available. Instead, I used one of the many events available in the Web Font Loader to update sessionStorage
, like this:
WebFontConfig = { // other options and settings active: function() { sessionStorage.fonts = true; } }
Don’t forget to take a look at the full list of available events. But with this particular event we can immediately check in the head of the document whether that key exists in the sessionStorage
and, if it does, we can then add the class name to the html
element as quickly as possible:
<head> <script> (function() { if (sessionStorage.fonts) { console.log("Fonts installed."); document.documentElement.classList.add('wf-active'); } else { console.log("No fonts installed."); } })(); </script> </head>
This doesn’t entirely block the flash of unstyled text which is probably going to be an issue either way, but it’s certainly an improvement on FOIT.
Font loading is crucial for good typography
After playing around with this technique I found that the experience completely changed my view about what good typography on the web is.
But if you happen to have any ideas on how to improve the method shown above then please let us know about them in the comments.
The
font-size-adjust
property can help with the FOUT and reflow. It only works in Firefox right now, but Chrome’s been actively working on it, because text reflow on mobile is expensive.I’ve didn’t like it when browsers started implementing FOIT, so I’m glad this is back on the radar. I always thought it was a vanity solution that wasn’t driven by usability. Hopefully browsers will start working to solve the real problem (losing your place because of reflow sucks) and ditch the aesthetically pretty but poor experience of hiding text until the font loads.
Living in Australia where slow internet is common (ADSL speeds for me and unlikely to change any time soon) the issue is quite apparent and when I do occasionally come across sites which use FOUT instead of FOIT I find it to be a better experience because I’d rather read the content than wait. It’s not like OS fonts inherently ugly, they’re just not “on-brand” for most sites.
FOIT would be fine if it really were a flash but more often than not for me it’s more of a long pause than a quick flash.
If I understand correctly, this approach loads the web fonts via the library (dynamically). Wouldn’t it be faster to load the fonts via the style sheet itself (
@font-face
), and then use tools like FontFaceObserver to “detect” when they’re ready in order to add thewf-active
class?I’m not sure what the performance differences would be between FontFace Observer and this technique, but I’d love to read more about it!
Yeah, Filament Group recommended it. It’s used to decrease the FOIT that browsers make when they think that they have the font ready.
Hi – interesting article! Is there a demo somewhere? Thanks :)
Great article Robin!
One thing puzzled my though. Not that it matters much either way, but why wasn’t the cookie option available to you with plain static content? If you have access to sessionStorage, what would prevent you from using Cookies in a JS setting?
Thanks! I did a little bit of research about this and lots of folks in StackOverflow suggested sticking to cookies for data that was being used server-side, whilst
localStorage
orsessionStorage
should be used for client-side data. There’s no problem doing this with cookies instead though, at least as far as I’m aware.Thanks for the clarification. I read the article as if you ran into a technical limitation vs. simply following a convention.
Great writeup! There is a small bug in your preconnect example: you need an additional “crossorigin” [1] attribute on the element to open the “right kind” (one without any other requests with credentials requests on them) of socket for fetching fonts. It’s confusing, I know…
[1] http://w3c.github.io/resource-hints/#preconnect
Thanks, Ilya! I’ve updated the post with your suggestion.
I like your
sessionStorage
approach much better than Filament Group’s cookie approach. But I’m still frustrated that web fonts have to be sooo difficult.I hope system fonts keep gaining traction. The modern Roboto, Segoe UI, San Francisco stack is actually decent in a lot of scenarios.
Curious… would you do this if you had a self-hosted webfont? We have to use 2 font faces for a project that add up to 400k (we could not talk them off this ledge).
Hi there. A few questions:
What if I have multiple fonts with the same family name but with different weights and styles. I have MY_FONT with weight 400, 500 and 700 + 400 and 500 italic. Can you make that work correctly?
And, what if the user doesn’t have JS enabled? Oh yeah, maybe trigger the @font-face font on .no-js.
I have all my fonts locally and don’t/can’t use the url for downloading the fonts.
Would it be possible to morph a webfont into another to avoid a flash? Like fade out->fade in ? Just an idea…