Is a js event emitted when a css font is swapped?

Viewed 510

Can I tell when a font has been successfully (or otherwise) loaded, and then act on that with JS?

Context

I'm printing a series of documents using playwright. I'm currently loading these fonts https://fonts.googleapis.com/css2?family=Raleway:wght@100;300;600&display=swap and sometimes the waitUntil="networkidle" will complete its ½ second wait before the font has loaded, resulting in the doc being printed in the fallback font.

References

I've had a read of this: Controlling Font Performance with font-display and this: Navigating & waiting, and done a lot of searching, but with no avail.

Possible work arounds

  • I could download the font to my computer so that it's a local asset, but that means remembering an extra step if I change my mind about the font in future. It also makes it harder to do on a remote machine.
  • I could add in a fat pad of >4 seconds as an explicit wait, but that adds 4×N seconds to the run time if N=the number of documents (6½ per 100 documents)
  • I could write a service worker to precache the fonts and then emit an event, but this is way more work than this deserves, and managing its lifecycle seems like a painful future.

Is there an easy way?

3 Answers

Yes you can do this with Font Face Observer which is a small @font-face to monitor the load of the font. This does not restrict you using any type of font loading.

for example

var font = new FontFaceObserver('My Family', {
  weight: 400
});

font.load().then(function () {
  console.log('Font is available');
}, function () {
  console.log('Font is not available');
});

If you want more information check out https://portalzine.de/dev/options-to-detect-when-a-font-face-has-been-loaded/

Hope that answered your question.

The link in christopher-holder's answer pointed me at that useful article from Portalzine. I used the technique from their first option, i.e.

alert('Roboto loaded? ' + document.fonts.check('1em Roboto'));  // false

document.fonts.ready.then(function () {
  alert('All fonts in use by visible text have loaded.');
   alert('Roboto loaded? ' + document.fonts.check('1em Roboto'));  // true
});

document.fonts.onloadingdone = function (fontFaceSetEvent) {
   alert('onloadingdone we have ' + fontFaceSetEvent.fontfaces.length + ' font faces loaded');
};

and moved the logic to the Playwright script using page.waitForFunction like this:

await page.goto(
  "file:///" + path.resolve(htmlFilename),
  (waitUntil = "networkidle")
);
await page.waitForFunction(() => document.fonts.check("1em Raleway"));

This waits for the page to finish loading, and for the network to be quiet for ½ a second, and then checks if the font is loaded.

FontFaceObserver looks nice, but this approach keeps the printing logic in the playwright script and doesn't touch the document itself, which feels cleaner.

This might be belt and braces, I'll update this answer once I've tested it more thoroughly.

Here's a piece of JS code that I'm using to re-adjust the scroll position to adjust for :target scroll-margin and web fonts that differ from fallback fonts:

if (location.hash)
{
    var targetElement = document.getElementById(location.hash.substring(1));
    if (targetElement && targetElement.scrollIntoView)
    {
        // scroll to correct position immediately
        targetElement.scrollIntoView({block:"start", behavior:"auto"});

        // Note that because the page might be rendered before
        // web fonts are ready and web fonts may/will cause
        // layout shift, we'll need to re-adjust the scroll
        // position when fonts are ready:
        try
        {
            var fontsReady = document.fonts.ready;
            fontsReady.then(function ()
            {
                console.log('Font loading complete');
                // process full event loop and re-adjust the scroll:
                window.setTimeout(function ()
                {
                    targetElement.scrollIntoView({block:"nearest", behavior:"smooth"});
                }, 0);
            });
        }
        catch (e)
        {
            console && console.error && console.error(e);
            console && console.log && console.log("This browser doesn't support observing font loading, scroll position may be incorrect.");
        }
    }
}

Depending on your use case and expected event timing both calls to scrollIntoView() could use behavior:"auto" or behavior:"smooth". Also note that the user may have scrolled the viewport between rendering the content with fallback fonts and web font loading completing so you may want to add additional code to listen for scroll events and avoid scrolling anything when web fonts are swapped in if user has already scrolled to another position.

Related