Immediate Font Fallback Rendering for Firefox and Webkit Browsers

Using custom fonts is easy, having excellent usability with them, sadly, is not. In this articles I’ll explain the issues I encountered, and analyze the different ways to resolve them.

Problems with Custom Fonts

The main problem is the long rendering time.

Long Text Rendering

In Firefox and all Webkit browsers, text that is styled to use the custom font does not render (with the fallback font) until the custom font is loaded. They do have a timeout for this behavior, which I read somewhere is 3 seconds (I don’t recall the actual source).
They do this to avoid FOUT (flash of unstyled text). In my opinion, this is horrible for the user experience. There are 2 paths to walk on:

1) Render the text with a fallback font, and rerender it with the correct font once it arrives
2) Render no text at all until the font arrives

The website rerenders in both cases, so why not let the user start skimming the text while the custom font is on the way?

This is one of the few times where Internet explorer and Edge work better than all other browsers, they render text with the fallback font immediately, and rerender when the custom font is loaded. So if your users only use these browsers, you can stop reading and get on with your lives, because this is exactly the behavior I try to achieve with the rest of the post.

The Solutions

Okay, enough ranting. But it had to be said.

I won’t not bother you with all the research I did, all the blogposts and stackoverflow answers I read, just the 2 solutions I could find and how they compare.
Please keep in mind that the solutions should at most use vanilla JavaScript. I’m sure there are many libraries out there that do exactly what I want, but for this particular website I tried to keep the size as small as humanly possible.

Solution 1 - Using Font Loading API

The Font Loading API is very new, so new that it isn’t even properly documented on MDN at the time of writing. In fact, I had to search for an example of using it to figure out how it works. But, thankfully, most major browsers don’t wait long to implement cool new features. You can use them in almost all major browsers, even on mobile.

Font Loading API CanIUse

Step 1 - Remove all References to the custom Font

Since browsers don’t render your text if there is a custom font specified for it, you need to remove that. A specification like this:

.text {
    font-family: "my awesome font", Helvetica, Arial, sans-serif;
}

becomes this:

.text {
    font-family: Helvetica, Arial, sans-serif;
}

Do this everywhere a custom font is defined and you don’t want to have empty text appear.

Step 2 - Load the custom Font

Now the browser won’t load the font. So we have to load it manually.

var myAwesomeFont = new FontFace("MyAwesomeFont", "url(fonts/MyAwesomeFont.ttf)");
myAwesomeFont.load().then(function () {
    document.fonts.add(myAwesomeFont);
});

Add that code to your page. It can run whenever you like, at the beginning, at the end of loading the page, after 5 seconds, doesn’t matter.

For the ones that aren’t just interested in copy&pasting code a short explanation:

First, you create a new FontFace object and pass the name (the one used in the CSS declaration) and its source. Make sure to wrap the url in url(), inside the string!
Quotation marks on the outside –> "url(fonts/MyAwesomeFont.ttf)" <– Quotation marks on the outside

Then, trigger loading of the font by calling the load method.
It returns a Promise, and by passing a function to its then method you can execute code after the loading finished.

The only thing left to do is adding the font to available list of fonts.

Step 3 - Add the custom Font to Elements

Since we removed all references to the custom font, we have to add it again. But this time in a manner that the browser will render the text immediately.
Basically, we need a way to add the font again as soon as it is loaded. A relatively easy way is to react to a specific class set on the body. In my case, this is fonts-loaded.

The CSS now looks like this:

.text {
    font-family: Helvetica, Arial, sans-serif;
}

body.fonts-loaded .text {
    font-family: "my awesome font", Helvetica, Arial, sans-serif;
}

Then, you add that class to the body after the font finished loading. This is done with

document.querySelector('body').classList.add('fonts-loaded');

The complete JavaScript code now looks like this:

var myAwesomeFont = new FontFace("MyAwesomeFont", "url(fonts/MyAwesomeFont.ttf)");
myAwesomeFont.load().then(function () {
    document.fonts.add(myAwesomeFont);
    document.querySelector('body').classList.add('fonts-loaded');
});

Voila, you have successfully implemented early rendering of text in Firefox, Chrome and all other Webkit browsers.

Step 4 - Ensuring Internet Explorer Compatibility

Just one last thing, if you implemented the above steps, you’ll realize that it doesn’t work in IE and Edge anymore, because they don’t yet implement the Font Loading API.
But since these 2 browsers do what we want anyway, we only have to set the fonts-loaded class on the body immediately.
The JavaScript now looks like this:

if (document.fonts != null) {   //test if the Font Loading API is available
    var myAwesomeFont = new FontFace("MyAwesomeFont", "url(fonts/MyAwesomeFont.ttf)");
    myAwesomeFont.load().then(function () {
        document.fonts.add(myAwesomeFont);
        document.querySelector('body').classList.add('fonts-loaded');
    });
} else {
    document.querySelector('body').classList.add('fonts-loaded');
}

Solution 2 - Font Inlining

Font inlining is a way to put the whole font in the CSS file, without the need for loading a different file. So lets dive right in!

Step 1 - Convert the Font to base64

This is the easiest to do, just head over to Font Squirrel and let them do the heavy lifting. Just make sure to select Expert to get the customization options and check Base64 Encode. The result is a zip file that includes the converted fonts, and a CSS file with the font declarations using base64.
You might want to change the names, so that it matches with the names you are using in the rest of your CSS.

For this to work properly you should create a separate CSS file for the font definitions. Lets call it font_definitions.css.

Step 2 - Remove all References to the custom Font

For this to work you also have to split up the font-family declarations in one with, and one without your custom font.
Same as with solution 1, step 1:

.text {
    font-family: "my awesome font", Helvetica, Arial, sans-serif;
}

becomes this:

.text {
    font-family: Helvetica, Arial, sans-serif;
}

Step 3 - Add the References to the custom Font

Now, add the references to the custom font to font_definitions.css. Ideally, they should have higher specificity than the original ones, so that they overwrite them. Otherwise, they must be linked AFTER the other CSS files.

This is how your font_definitions.css file should look now:

@font-face {
    font-family: 'my awesome font';
    src: url(data:font/truetype;charset=utf-8;base64,<<copied base64 string>>) format('truetype');
    font-weight: normal;
    font-style: normal;
}

body .text {    //add body for higher specificity
    font-family: "my awesome font", Helvetica, Arial, sans-serif;
}

Step 4 - Lazy load font_definitions.css

Inlining the font without lazy loading that particular CSS file isn’t going to achieve anything, since the browser will wait until the CSS file is loaded before starting to render.

The easiest way I found to lazy load CSS files is to set the media attribute of the link to none, and set it to all in the load event.

The link would look like this:

<link type="text/css" rel="stylesheet" href="css/font_definitions.css" media="none" onload="this.media='all'" />

Voila, you have successfully implemented early rendering of text in Firefox, Chrome and all other Webkit browsers.

UPDATE: Solution 3 - using font-display

There is a new attribute for font faces, called font-display. It allows to alter the duration the text is rendered invisibly if the primary font is not loaded.
The value to achieve the desired effect of this article is swap. It shows the fallback font immediately.

Unfortunately, at the time of writing, only Chrome and Opera support that feature, Firefox behind a flag.

@font-face {
    font-family: 'my awesome font';
    src: url(data:font/truetype;charset=utf-8;base64,<<copied base64 string>>) format('truetype');
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

Solution Comparison

The best solution would be using font-display, hands down, but it seems there is still some time needed until it is supported by all browsers.

The other two solutions aren’t ideal, but work with current browser versions. Both don’t work without JavaScript, although Font Inlining requires considerably less. Font Inlining is less work too. That said, I prefer Using the Font Loading API, first and foremost because of quite a few shortcomings of Font Inlining regarding performance, but also because I prefer the font-family declarations to be where I want them to be, not being forced to put them in font_definitons.css.

Conclusion

The web is not perfect, we all know that. Opinions differ, and so differ browser implementations. I hope I have helped you with the solutions outlined above to unify the browsing experience for all your users for the better.
If you have any improvement suggestions or questions, please don’t hesitate to leave a comment.