Should I Use JavaScript to Load My Web Fonts?

When they were first introduced, web fonts were primarily a CSS feature, and for many web developers they’ve always loaded them using CSS (and nothing more). But in the last decade, default web font loading behavior in many browsers made CSS-only methods a gamble with our page’s text render, and thoughtful developers switched to safer JavaScript methods. Recently, browser support for new and safer CSS-only strategies have left some developers wondering: are JavaScript methods to load web fonts necessary? Are they useful? Let’s dig in.

The Epochs of Responsible Web Font Loading

Permalink to 'The Epochs of Responsible Web Font Loading'

1997: CSS-only web fonts were good

Permalink to '1997: CSS-only web fonts were good'

Internet Explorer was the only browser that supported web fonts and did so in a progressive-enhancement friendly way that—in hindsight—seems remarkably forward-thinking. When a web font loads in Internet Explorer, the fallback text is visible during load: robust and readable.

Permalink to '2008: JavaScript for web fonts recommended'

Web font support was added to Safari in 2008. Unfortunately, Safari chose to make all web font text invisible text while the font file was loading. Notably this invisible text did not include a sane timeout for these requests—turning web font requests into a single point of failure for text on the page. If your request for the font file stalled—your text could be invisible indefinitely offering little recourse to the user except to start over and reload the entire page. As such, JavaScript methods were needed to work around this problem and to load web fonts responsibly.

2016: CSS-only web fonts weren’t terrible

Permalink to '2016: CSS-only web fonts weren’t terrible'

Firefox was the first web browser to introduce a invisibility timeout to their font loading in 2011—if the web font hasn’t loaded in three seconds, the fallback font is rendered. Chrome followed suit in 2014 and Safari was the last major browser to adopt the timeout in 2016. Notably, this marked the year in which CSS-only methods to load web fonts were no longer a single point of failure for web pages. However, developers still had limited control over text visibility while the font was loading.

Read a more detailed account of web font loading history.

2018: CSS-only is good again!

Permalink to '2018: CSS-only is good again!'

2018 marked a new epoch of font loading behavior with the adoption of font-display. Our CSS-only savior, font-display is a simple CSS descriptor that allows developers to take back control over the text invisibility behavior of web font loading.

@font-face {
font-family: Noto Serif;
src: url(notoserif.woff2) format('woff2'),
url(notoserif.woff) format('woff');
font-display: swap;
}

With burgeoning browser support, font-display alleviates JavaScript as a requirement for visible text while the font loads! This makes for a great, simple baseline for font loading that will be “good enough” for many sites.

Learn more at Monica Dinculescu’s font-display page on Glitch.

Why Would You Still Use JavaScript?

Permalink to 'Why Would You Still Use JavaScript?'

It is true that using the font-display descriptor can get you to an acceptable level of web font loading—but we can do better! Let’s look at some additional improvements we can make:

1. Group Repaints

Permalink to '1. Group Repaints'

When you load multiple font files for a single typeface, each request can incur a separate repaint and reflow. Reduce the amount of movement on your page by grouping this into a single repaint. You can read more about this at The Problem with font-display and Reflow.

Your JavaScript for this might look something like:

if ("fonts" in document) {
var font = new FontFace(
"Noto Serif",
"url(notoserif.woff2) format('woff2'), url(notoserif.woff) format('woff')"
);

var fontBold = new FontFace(
"Noto Serif",
"url(notoserif-bold.woff2) format('woff2'), url(notoserif-bold.woff) format('woff')",
{ weight: "700" }
);

Promise.all([
font.load(),
fontBold.load()
]).then(function(loadedFonts) {

// Render them at the same time
loadedFonts.forEach(function(font) {
document.fonts.add(font);
});
});
}

2. Adapt to User Preferences

Permalink to '2. Adapt to User Preferences'

You can use the Save-Data preference to opt-out of fonts when the user has enabled Data Saver mode. Read more at Delivering Fast and Light Applications with Save-Data on Google’s Web Fundamentals.

function loadFonts() {
/* NOTE: Reuse the Group Repaints code snippet above, here */
}

if( navigator.connection &&
navigator.connection.saveData ) {
} else {
loadFonts();
}

Or, you can use the prefers-reduced-motion preference (Browser support) to opt-out of web font loading when the user has enabled the Reduce Motion accessibility option.

To enable this on Mac OS, go to System Preferences > Accessibility > Reduce Motion. On iOS it’s Settings > General > Accessibility > Reduce Motion.

It might be a stretch to say that web font reflow is motion—but it would be an improvement to page stability. Read more at An Introduction to the Reduced Motion Media Query on CSS-Tricks.

function loadFonts() {
/* NOTE: Reuse the Group Repaints code snippet above, here */
}

if( "matchMedia" in window &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches ) {
// do nothing
} else {
loadFonts();
}

For extra credit, I could imagine using the Paint Timing API to only opt-out of rendering if the web font finishes loading after the first-paint entry has been logged. Read more at the lovely post: PerformanceObserver and Paint Timing API written by José M. Pérez. But truthfully this might be a little much, even for me.

3. Adapt to User Context

Permalink to '3. Adapt to User Context'

You can also use the Network Information API (Browser support) to opt out of fonts on slow connections. Read more at the Network Information API documentation on the MDN web docs.

function loadFonts() {
/* NOTE: Reuse the Group Repaints code snippet above, here */
}

if( navigator.connection &&
( navigator.connection.effectiveType === "slow-2g" ||
navigator.connection.effectiveType === "2g" ) ) {
// do nothing
} else {
loadFonts();
}

Note that you may be tempted to subscribe to the "change" event exposed in the Network Information API. Careful! You don’t want your web font load to trigger super-late after the user may already be knee-deep in reading! The later a web font reflow happens, the more disruptive it feels.

4. Using Third Party Hosts

Permalink to '4. Using Third Party Hosts'

Unfortunately at time of writing no known third party web font host supports the CSS-only font-display descriptor. I’d recommend sticking with JavaScript approaches when using these services for now.

JavaScript isn’t going away

Permalink to 'JavaScript isn’t going away'

As you can see, the advanced web font loading control offered by JavaScript still provides more than sufficient value to keep it around. You can adapt your page’s performance profile to suit a user’s network conditions, user preferences, improving the general loading behavior of self hosted fonts and third party hosting providers.

Do you have another idea? Let us know @filamentgroup on Twitter.

All blog posts