107

I'm using @font-face to embed fonts in my website. First the text renders as the system default, and then (once the font file has loaded presumably) the correct font renders a fraction of a second later. Is there a way to minimise/get rid of this delay, by delaying the page rendering until after fonts have loaded or similar.

wheresrhys
  • 22,558
  • 19
  • 94
  • 162
  • Caching fonts would work. – Krii May 03 '15 at 03:03
  • Keep in mind that this is a good way to make your web site appear really slow, because users on a wireless link may take a while to load the web fonts. Many users will just hit the back button if text doesn't show up in a couple seconds. – erjiang Jun 04 '15 at 14:12
  • Does this answer your question? [How to be notified once a web font has loaded](https://stackoverflow.com/questions/5680013/how-to-be-notified-once-a-web-font-has-loaded) – Zach Saucier Mar 28 '20 at 16:34
  • @erjiang - OP didn't say "prevent *anything* from rendering until the fonts are loaded". OP also stated "fraction of a second", not "a couple seconds". Just sayin. – ashleedawg Dec 30 '22 at 09:08

13 Answers13

69

Since nobody mentioned that, I believe this question needs an update. The way I managed to solve the problem was using the "preload" option supported by modern browsers.

In case someone does not need to support old browsers.

<link rel="preload" href="assets/fonts/xxx.woff" as="font" type="font/woff" crossorigin>

some useful links with more details:

https://developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content http://www.bramstein.com/writing/preload-hints-for-web-fonts.html

Thiago C. S Ventura
  • 2,448
  • 1
  • 29
  • 43
  • 4
    this is the best answer, custom font don't load when its not used YET. This one loads it right away so no box fonts appearing when first time using fonts – Jones G Aug 29 '20 at 06:52
  • Works perfectly on Chrome 91, Safari 14, Firefox 89 & Opera 77! – Mohit Singh Jun 12 '21 at 13:17
  • I have a script that relies on the actual texts' width and height, but it takes them from before the custom font is applied. This fixes it! – Jjj Jul 14 '21 at 14:54
  • 4
    This would preload the font and make it likely to be available when the DOM is rendered. However, since it's not blocking, it still can't guarantee that the custom font is loaded before the page render. – zeroliu Dec 01 '21 at 06:42
36

Edit: The best approach is probably to base64 encode your fonts. This means your font will have to be loaded fully by the time your HTML is parsed and displayed. You can do this with font squirrel's webfont generator https://www.fontsquirrel.com/tools/webfont-generator by clicking "Expert" and then "base64 encode". This is how services like TypeKit work.


Original answer: Another way to detect if fonts are loaded would be using FontLoader https://github.com/smnh/FontLoader or by copying their strategy.

They bind to the scroll event in the browser, because when the font loads it will resize the text. It uses two containing divs (that will scroll when the height changes) and a separate fallback for IE.

An alternative is to check the DOM periodically with setInterval, but using javascript events is far faster and superior.

Obviously, you might do something like set the opacity of body to 0 and then display it in once the font loads.

Ryan Taylor
  • 12,559
  • 2
  • 39
  • 34
  • I tried to insert base64 encoded font into ` – tsh Sep 24 '21 at 06:43
  • Thanks a lot! This base64 encoding was the only way to make that text flashing go away for me on Firefox. I tried everything like waiting for the `onloadingdone` event, for `document.fonts.ready`, checking the font with `fonts.check(...)`, and even when all of them said "yes, the font is loaded", Firefox would still flash with the fallback font first. This fontsquirrel thingy and base64 solved it... – Elmar Zander Dec 08 '22 at 12:40
  • this will greatly increase the css size. – peixotorms Mar 04 '23 at 13:39
26

This is down to how the browser behaves.

First off where is your @font declared? Is it inline to your HTML, declared in a CSS sheet on the page, or (hopefully) declared in an external CSS sheet?

If it is not in an external sheet, try moving it to one (this is better practice anyway usually).

If this doesn't help, you need to ask yourself is the fraction of a second difference really significantly detrimental to the user experience? If it is, then consider JavaScript, there are a few things you might be able to do, redirects, pauses etc, but these might actually be worse than the original problem. Worse for users, and worse to maintain.

This link might help:

http://paulirish.com/2009/fighting-the-font-face-fout/

Lawrence Dol
  • 63,018
  • 25
  • 139
  • 189
Tom Gullen
  • 61,249
  • 84
  • 283
  • 456
  • 1
    The best thing you can do is follow Tom's advice and, additionally, make sure your clients aren't hitting the Web server on subsequent page loads, because that's a real font rendering blocker--set an expiration header for the CSS file and a whooping one on the actual font resource and your good to go. – Filip Dupanović Jan 17 '11 at 11:02
  • Nice article. Lets hope Firefox adopts the safari approach to loading fonts in a not too far distant release. The FOUT hasn't been a problem for me in the past, but for the current site I'm working on the custom font is very different to any of the system fonts, so for the first time the flash is very noticeable. – wheresrhys Jan 17 '11 at 11:06
  • 1
    A large font (i.e. 200 Kb) and a slow connection (i.e. 3G) is not a fraction of a second. Designers should consider this scenario for those with tablets with a SIM chip. – Jaime Mar 30 '16 at 18:18
6

This code works very well for me. It uses the Font Loading API which has good support among modern browsers.

<style>
  @font-face {
    font-family: 'DemoFont';
    font-style: normal;
    font-weight: 400;
    src: url("./fonts/DemoFont.eot");
    src: url("./fonts/DemoFont.woff2") format("woff2"),
    url("./fonts/DemoFont.woff") format("woff"),
    url("./fonts/DemoFont.ttf") format("truetype");
  }

  .font {
    font-family: 'DemoFont';
    color: transparent;
  }

  html.font-loaded .font {
    color: inherit; // Override `transparent` from .font
  }

</style>
<script>
  // Check if API exists
  if (document && document.fonts) {    
    // Do not block page loading
    setTimeout(function () {           
      document.fonts.load('16px "DemoFont"').then(() => {
        // Make font using elements visible
        document.documentElement.classList.add('font-loaded') 
      })
    }, 0)
  } else {
    // Fallback if API does not exist 
    document.documentElement.classList.add('font-loaded') 
  }
</script>

The trick is to set the CSS color to transparent for elements using the font. Once loaded this is reset by adding font-loaded class to <html> element.

Please replace DemoFont with something meaningful for your project to get it work.

Holtwick
  • 1,849
  • 23
  • 29
  • 2
    one problem with this is that `fonts.load('10px Nothing')` will happily resolve with an empty list of fonts. You'd need to combine `load` and `check`. – Fluffy Oct 04 '20 at 09:13
6

I had a similar problem while rendering to an HTML canvas, and this was my solution. It's based on the FontFace API, and similar to Holtwicks approach. The key differences are that this is a generic approach and that it will work out-of-the-box for external fonts/stylesheets (e.g. google fonts).

A couple of notes; fonts.load( ... ) will happily resolve with an empty set of fonts if the font isn't known yet. Presumably, this happens if this code is called before the stylesheet declaring the font was added. I added a fonts.check(...) to overcome that.

This will let you await javascript execution until a font is available, so it won't work out of the box for 'normal' HTML content. You can combine this with Holtwicks answer above.

export async function waitForFontLoad(
    font: string,
    timeout = 1000,
    interval = 10
) {
    return new Promise((resolve, reject) => {
        // repeatedly poll check
        const poller = setInterval(async () => {
            try {
                await document.fonts.load(font);
            } catch (err) {
                reject(err);
            }
            if (document.fonts.check(font)) {
                clearInterval(poller);
                resolve(true);
            }
        }, interval);
        setTimeout(() => clearInterval(poller), timeout);
    });
}
Fluffy
  • 135
  • 1
  • 7
6

Joni Korpi has a nice article on loading fonts before the rest of the page.

http://jonikorpi.com/a-smoother-page-load/

He also uses a loading.gif to alleviate the delay so users won't get frustrated.

davecave
  • 4,698
  • 6
  • 26
  • 32
3

Use https://github.com/typekit/webfontloader

and check the events in the configuration https://github.com/typekit/webfontloader#configuration

<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
<script>
    WebFont.load({
        custom: {
            families: [ "CustomFont1", "CustomFont2" ]
        },
        active: function() {
            //Render your page
        }
    });
</script>
Santa Claus
  • 984
  • 1
  • 12
  • 26
klodoma
  • 4,181
  • 1
  • 31
  • 42
3

Only IE loads first the font and then the rest of the page. The other browsers load things concurrently for a reason. Imagine that there's a problem with the server hosting the font or with the font downloading. You will hang your entire site until the font is loaded. On my opinion a flash of unstyled text is better than not seeing the site at all

nunopolonia
  • 14,157
  • 3
  • 26
  • 29
3

You can use CSS font-display inside your @font-face. The keywords for all the available values are:

  • auto
  • block
  • swap
  • fallback
  • optional

Giulio Mainardi has written a nice article about all of them, and which you should use where on sitepoint.

You can read it here: https://www.sitepoint.com/css-font-display-future-font-rendering-web/?utm_source=frontendfocus&utm_medium=email

Lirianna
  • 330
  • 1
  • 13
  • 1
    This is the proper and preferred way to do this in 2020. Don't punish the user for having a spotty connection, but use `font-display: block` if you *really* must. Definitely don't do [what Ryan Taylor suggested](https://stackoverflow.com/a/23794798/9996911) and base64-encode anything; no user wants to have to download 30% more data just so you can block rendering until the download is complete, even (nay, *especially*, when on a mobile connection) if it's "only" a matter of e.g. 300kB. – Jivan Pal Oct 25 '20 at 00:01
1

while the answer posted by @fluffy works. But the interval function runs after every interval and doesn't wait for fonts.load promise to resolve, better solution would be to use recursive function

function waitForFontLoad(font: string, timeout = 1000, interval = 10) {
  const startTime = Date.now();

  return new Promise((resolve, reject) => {
    const recursiveFn = () => {
      const currTime = Date.now();

      if (currTime - startTime >= timeout) {
        reject("font listener timeout " + font);
      } else {
        document.fonts
          .load(font)
          .then((fonts) => {
            if (fonts.length >= 1) {
              resolve(true);
            } else {
              setTimeout(recursiveFn, interval);
            }
          })
          .catch((err) => {
            reject(err);
          });
      }
    };
    recursiveFn();
  });
}

reference - webfontloader

Himanshu Patil
  • 536
  • 1
  • 6
  • 18
1

Simplest solution

// Single font
FontLoad( ['Font name one'])

// Multiple fonts
FontLoad( ['Font name one','Another font name'])

you can use without CallBack funtion too, callback function is optional incase you want invoked inside when FontLoad function completed.

const FontLoad = async ( fonts=[] , callback=()=>{} ) => {
    await fonts;
    for (const font of fonts) {
        document.fonts.check(`80px ${font}`)
            ? document.fonts.load(`80px ${font}`).then( () => { console.log( `Font: ${font} loaded ✔️` ) } )
            : console.log( `Font: ${font} not founded ❌` )
    }
    document.fonts.ready.then(() => { console.log("Ready"); callback() })
}
  

  FontLoad( ['Arial','FONT_NOT_FOUNDED'], ()=> console.log("External function") )
GMKHussain
  • 3,342
  • 1
  • 21
  • 19
-4
(function() {
        document.getElementsByTagName("html")[0].setAttribute("class","wf-loading")
        document.getElementsByTagName("html")[0].setAttribute("className","wf-loading")
    })();

use this method.. use with Webfont.js

-9

Maybe something like this:

$("body").html("<img src='ajax-loader.gif' />");

Then when the page loads, replace body's content with the actual content and hopefully, fully rendered fonts, you may have to play around with this though...

benhowdle89
  • 36,900
  • 69
  • 202
  • 331