Anthony McLin

Drupal Performance: Eliminating the Flash of Invisible Text

Today there are a plethora of fonts available for use on the web. All major browsers support web font technologies, and there are many foundaries and freely licensed fonts available for making a site look just so. We no longer have to deal with the extremely limiting set of baseline fonts installed on visitors' computers. However, the rise of webfonts also means that there is a performance impact to using them. When the browser loads the site, it also has to load the fonts, just like any other static asset. This introduces a delay in the page render that can be characterised in one of two ways:

  1. Flash of Invisible Text (FoIT)
  2. Flash of Unstyled Content (FoUT)

For a long time developers had to content with the Flash of Unstyled Content as the page would start drawing before the CSS was fully loaded. The resulting effect was that the visitor would see the contents of the page, but no styles. While this was good from a performance standpoint, and proof of progressive enhancement, it often left a bad perception for visitors and probably more commonly, a negative perception by site owners of their own properties.

As developers and browsers improved page loads, the FoUC is largely mitigated, but when coupled with web fonts, the Flash of Invisible Text (FoIT) is becoming more prevalent. So much so, that Google's Lighthouse performance audit now identifies it as problematic.

The FoIT is caused by the page rendering with CSS, but before web fonts are loaded. If you are using a Javascript-based webfont you are very likely to be seeing this. Even with CSS-only webfonts and self-hosted webfonts, this still occurs simply because it takes time for the browser to load things. Even if you specify a safe fallback font, like "sans-serif", the browser will choose to display your text as invisible until the specified font is loaded and available.

The most robust solution available currently is using the Font Face Observer library. The way it works is that you delegeate the font loading to Javascript, so that it can be handled after domReady. In your CSS you specify two stylies for your fonts - the baseline styles (generic native fonts) used before the fonts are loaded, and then the final font styles for when the fonts are loaded. Javascript handles loading the fonts, and once loaded, adds a class on the root HTML node so that the browser switches which fonts are used.

/* CSS */
h1 {
  font-family: sans-serif; /* Default font to use while page is loading */
}
.fonts-loaded h1 {
  font-family: My Custom WebFont, sans-serif;
}

As a result, the visitor can read the page contents while the font is still loading, and when it appears, the text flickers as it updates to the new setting. As long as the default font and the loaded font have a similar visual size, the font replacement shouldn't be disruptive and the visitor should have a positive browsing experience.

Applying Font Face Observer with Druapl

Among many other features, the Drupal module Advanced JS/CSS Aggregation contains a Font Face Observer implementation that can streamline this performance bottlneck on your site. Once it is installed, you'll need to enable the module AdvAgg Async Font Loader:

You will also need to add the Font Face Observer library to your site. Make sure you have the Drupal Libraries module installed, download fontfaceobserver.js and package.json from the Font Face Observer project on github, and place them in your sites/all/libraries in a new folder called fontfaceobserver:

Once the files are in place, flush the libraries cache, and then the AdvAgg Async Font Loader configuration will show you the options available:

Set the options for the best performance:

  1. Loading should be "Local file included in aggregate". This will bundle up the Javascript into your site's JS files which reduces the number of HTTP requests and prevents making 3rd party DNS lookups to get a remote copy of the module.
  2. Enable "Use localStorage". If the visitor's browser is capable of localStorage, then a flag is set there so telling the plugin that the fonts are downloaded and cached, removing the flash of text as the visitor navigates to subsequent pages of your site
  3. Disable "Set a Cookie". This does the same thing as localStorage, but for browsers that don't support localStorage. If you dont need to support older browsers or users with localStorage disabled, then you can enable this to support them by sacrificing a little performance overhead
  4. Disable Prevent the Flash of Unstyled Text. The entire point of this performance enhancement is to replace a Flash of Invisible Text with a better-performing Flash of Unstyled Text. If we were to disabled the FoUT, then we wouldn't get the performance benefits.

After setting all the options, flush all caches and review your site for any issues.

Before:

After:

Yikes, that's not good! While I’m getting a performance gain from the deferred font loading, it’s totally hosing my font selections and all my content looks wrong. Why? Well here’s an interesting tidbit in the plugin configuration notes. "Async Loaded Fonts in CSS: Assumes quoted fonts will be downloaded and unquoated fonts are fallbacks"

What exactly does this mean? The plugin works by rewriting your CSS, and so to figure out what fonts will be downloaded as webfonts, and which are fallbacks to be loaded from the visitors computer, the plugin makes an assumption based on the naming conventions in your CSS:

.foo {
  font-family: My Custom WebFont, sans-serif; /* Plugin assumes My Custom WebFont and sans-serif are both fallback native fonts that visitors will have locally
}
.bar {
  font-family: "My Custom WebFont", sans-serif; /* Plugin assumes My Custom WebFont will be downloaded dynamically, but sans-serif will come from the visitors' local browsers
}

The plugin is assuming that any font that is named using quotes is a font that should be dynamically loaded, and any font without quotes is one that is provided as a local fallback. This is a really bad assumption. For one thing, if you're using a code linter for your CSS (which you should), or a preprocessor licke SCSS/SASS (which you should), then you most likely don't have a mix of quoted and unquoted font names since that would be an error in your styling rules. It just so happens that I'm not using any CSS preprocessing at this time, so I can clean this up, but I'm going to have to keep this mind for the future. For now, I'll follow their styling rule as bad as I think it is and make the adjustments in my stylesheets. Open Sans and Source Sans Pro are the two fonts I'm loading as webfonts, all the rest are local fallbacks, so I make the following changes:

font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
/* becomes */
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;

font-family: 'Open Sans',CG,"Century Gothic","Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, sans-serif;
/* becomes */
font-family: 'Open Sans',CG,Century Gothic,Lucida Grande,Lucida Sans Unicode, Helvetica, Arial, sans-serif;

font-family: "Monaco", "Lucida Console", Courier, mono;
/* becomes */
font-family: Monaco, Lucida Console, Courier, mono;

font-family: 'Source Sans Pro', sans;
/* becomes */
font-family: 'Source Sans Pro', sans-serif;

After making the changes, flush all the caches again and let's take another look to see that the local and remote fonts are all now displaying properly:

Caveats

The Drupal admin looks a little funky after applying this plugin because the theme I'm using there (Seven) are aftected by the rules being rewritten in this plugin due to the issue I described with the qutoed vs unquoted font names. Modifying the core themes generally isn't a good idea as your changes will get blown away by updates. Other admin themes may not suffer the issue. I can live with the outcome here as it's only a minor visual difference in the admin screens, and my public-facing pages are all benefitng from the performance improvement.

Resulting Performance Improvement

Let's run Lighthouse again. It shows an improvement, but I've only reduced one warning about "Text is invisible while webfonts are loading":

Why am I only getting a reduction in a single font? Why not the others? The AdvAgg Async Font Loader plugin works by rewriting the CSS files in your site theme and the Drupal core. It searches for any instances of @font-face used to define fonts and dynamically rewrites them to support the Javascript defered loading. Naturally it can only do this with CSS files locally hosted. If you are remote linking to Google Fonts, it cannot find and parse those entries, and so they won't benefit from this feature. However, any locally hosted webfonts should work just fine.

CSS Only Approach

There is a new CSS property that will allow you to mitigate this in the future purely with CSS properties. font-display can be used to specify how a font will behave on screen while the font file is being loaded. Browser support for font-display support is limited at the moment, but it's worth enabling for those browsers that can take advantage of it.

Now here's the catch with font-display: I can’t actually enable that since I don’t generate that CSS file for the font. Google Fonts service does. So Google Lighthouse is recommending a performance change that Google Fonts doesn’t provide. Some more research comes up with this thread for supporting font-display on Google Fonts. It’s apparently something they are working on, which means I’ll get this for free once they fix it. But until then, not much I can do.

Update: A workaround to use font-display with Google Web Fonts

 

 

Add new comment