By Scott Jehl
If you're loading fonts from a popular third party provider like Google Fonts or Typekit, the stylesheet link
-based loading snippets they offer are not great from a performance perspective. Like any ordinary stylesheet, they block page rendering while they are loading, and for all that delay, the CSS you get merely contains font-face
definitions for fonts that may be subsequently downloaded if they're used in that page. These steps add up and can make it hard to keep sites rendering quickly.
An ideal font-loading convention is to have actual @font-face
rules that we can drop into our own CSS file or style
element, but few font service providers offer that as a supported embed option (I filed an issue!), and it can be hard (or impossible) to pull it off on our own. Alternative approaches like loading their CSS asynchronously can help improve page rendering speed, but they tend to work best when you want font loading behavior that displays text using a fallback font while the font finishes loading (also known as font-display: swap
). Sometimes folks want that behavior and that's fine, but these days I typically prefer the browser's default font-display: block
pattern, which keeps text invisible for up to 3 seconds to allow the custom font to load before showing a fallback. Admittedly, I was a "swap" fan when it became available, but as browsers standardized on shorter blocking timeouts and network speeds improved, the default block behavior does tend to feel less jumpy to my eye.
A Better Compromise
With this situation in mind, I wanted to document an option that allows me to get the best of both worlds. When I need to load fonts from one of these third party services, this approach allows me to load the relevant files asynchronously while the page renders, while still hiding text for up to a few seconds to give the font a chance to load first. It works by using an asynchronous JavaScript font loader such as FontFaceObserver, or even Webfontloader, which Typekit has officially supported for several years. Admittedly, using these loader scripts isn't particularly novel, but a nice thing about them is that in addition to asynchronously loading their resources, they can be used to apply classes to the HTML element that we can use to style our page while the font is loading and applying.
In fact, Typekit's version of the snippet adds those classes for you, and one class in particular really helps for this approach: wf-loading
. That class is applied to the HTML element immediately and it stays there until custom fonts are loaded and ready to show, or for 3 seconds–whichever comes first. This means we can use that class to style our text in a manner that mimics a desirable font-display: block
behavior.
To pull off the "invisible type" effect using that class, I like to use color: transparent
because color
styles the text itself while allowing other parts of the interface to be visible (unlike say, opacity
, visibility
, or display
). It's not perfect, but it's close enough for this temporary state in many cases.
Here's how I use that class to hide text for at most 3 seconds while the fonts are loading:
<style>
.wf-loading * {
color: transparent !important;
}
</style>
Next, I need to pair that with a font loading JavaScript snippet. Again, it's possible to use these font-loaders with many providers but for this example, I'll show how to use it with Typekit's default asynchronous loader:
<script>
(function(d) {
var config = {
kitId: 'KIT-ID-HERE',
scriptTimeout: 3000,
async: true
},
h=d.documentElement,t=setTimeout(function(){h.className=h.className.replace(/\bwf-loading\b/g,"")+" wf-inactive";},config.scriptTimeout),tk=d.createElement("script"),f=false,s=d.getElementsByTagName("script")[0],a;h.className+=" wf-loading";tk.src='https://use.typekit.net/'+config.kitId+'.js';tk.async=true;tk.onload=tk.onreadystatechange=function(){a=this.readyState;if(f||a&&a!="complete"&&a!="loaded")return;f=true;clearTimeout(t);try{Typekit.load(config)}catch(e){}};s.parentNode.insertBefore(tk,s)
})(document);
</script>
<style>
.wf-loading * {
color: transparent !important;
}
</style>
If you're using Typekit, be sure to add your "kit id" where it says to and you're ready to start styling elements in the page with the custom font. If you're using another service, check out the integration options for FontFaceObserver and others to bend this pattern to your needs.
Demos and Comparison
Here's a demo page where you can see the approach in action. You may need to throttle your browser to a 4G connection to see any font-blocking, but on slower connections you should see layout boxes render right away while the fonts are loading. Async Typekit Demo
Here's a screenshot of a visual page loading comparison in default "Lighthouse" mobile settings between one page that loads fonts using Typekit's default link
embed, vs a second one using the embed described in this post.
As you can see, the text ends up rendering at a similar moment in both timelines, but the second demo was able to render its layout 1.5 seconds earlier, which is great for perceived performance as well as Core Web Vitals.
Enjoy
That's the end of the post, bye.