Anthony McLin

CSS-only solution for Flash of Invisible Text using font-display

My recently article on resolving the Flash of Invisible Text in Drupal left me wanting. It was never fully resolved since I was using Google Web Fonts, and they haven’t specified the CSS font-display property in their stylesheets. Using a pure CSS solution will have much better performance than Javascript, so I decided to do deeper investigation to see if this is solvable without self-hosting the webfonts.

There’s three things I want to test:

  1. Continue to load the Google webfonts using <link>, and add the font-display property in my local stylesheet
  2. Load the Google webfonts using @import, and add the font-display property in my local stylesheet
  3. If neither works, add font-display property to a local copy of the Google font styesheet, but don’t self-host the font files

Even if you are running a CDN, there's value in loading the webfont from Google’s CDN since there’s a good chance your site visitors will already have the font in their browser cache, thereby improving your site speed.

Setup

  1. On a local copy of my site, I started by disabling the Font Observer approach for loading fonts via Javascript on Drupal, and flushed all my site caches.
  2. Opened up the local site in a Chrome Incognito window, and opened the Lighthouse Audit tab in Dev Tools
  3. Enabled throttling in the audit settings so it’s possible to review the font loading

At this point, my code is effectively structured like this:

See the Pen Rqvwvg by Anthony McLin (@amclin) on CodePen.

Once the preliminaries are in place, I run an audit to measure the current state, and make sure that Lighthouse is flagging the invisible text issue:

The issue is getting flagged, and in the render timeline view I can clearly see the invisible text state (highlighted in red in this screenshot), so I can proceed to the test scenarios.

Test 1: <link> remote font stylesheet with font-display injected locally

For this test, I’m going to keep the <link> tag in place in the HTML, and add the font-display property in my local CSS files. My reasoning is that the browser knows how to merge all the multiple @font-face declarations together, and so perhaps if I have a block that only defines the font-display property, it will effectively merge it with the others. If this works, it would be simplest solution with the least amount of code.

The code is structured like this:

See the Pen EOraxV by Anthony McLin (@amclin) on CodePen.

When I run the Lighthouse performance audit, the result is the same as the baseline. This did not do anything to alter how the text displays during webfont load, and report flagged this as an issue. This solution will not work.

Test 2: @import remote font stylesheet with font-display injected locally

For this test, I’m going to remove the <link> tag from the document head and replace it with @import from my CSS files instead, but still try to use my @font-face declaration to append the font-display properly. I still think the merging of the CSS properties will work, but perhaps it isn’t happening quick enough because the remote CSS is provided separately via <link> instead of @import.

The code is structured like this:

See the Pen jQdEOd by Anthony McLin (@amclin) on CodePen.

This time the Lighthouse performance audit shows that the issue is solved! The diagnostic warning about invisible text during font load shows success!

But there’s something strange about the render timeline:

There’s still a set of frames where text is invisible. What’s going on? After more testing and narrowing down of the code necessary to replicate this scenario, I’m convinced there is a bug in Lighthouse causing it to incorrectly mark the scenario as passing when it should be failing the invisible text during webfont load test.

Therefore this solution won’t work, because I want the real performance gains, not the false results of an automated report.

Test 3: Local font stylesheet with remote font files

For this test, I’m going to copy the remote stylesheet from Google and host it locally. That way, I can add the font-display property to every @font-face entry in the stylesheet. I’m not going to host the font files themselves locally, as I still want to maximize the possibility of them being in my visitors’ cache already when they first get to my site.

The code is structured like this:

See the Pen qQgEEa by Anthony McLin (@amclin) on CodePen.

When running the Lighthouse audit, it is clear that this is the complete solution that actually leverages font-display as it is intended to be used. The audit shows a passing state:

And the render timeline shows that the text is changing faces without having a flash of invisible content:

Note that this solution does have its risk. If Google changes the paths to any of the font files they could start returning a 404, which would break styling on my site and degrade my site performance. I’m trusting that the’ll keep the font files hosted as-is and put in place any redirects. Alternatively I could self-host all the font files, but then I lose the value of them already being cached in my visitors' browsers.

Applying font-display to Drupal Theme

Since the third scenario is the one that actually works, I’m going to apply this to my Drupal theme. I remove the original <link> injection from my template.php, and add a new fonts.css file where I copy the Google CSS and add the font-display. You can see the specific changes on GitHub.

Summary

Don’t just take automated test results for granted. Review all the information and observe the render timelines and network activity. Good performance comes from a thorough understanding all the interdependent pieces.

Add new comment