javascriptfont-facecssom

Browsers lazy-loading fonts with FontFace


So, I'm developing a simple web game with the Canvas API. I need to draw characters in the canvas using specified fonts.

In the loading routine, I use promises to wait for the fonts I need, like this:

Promise.all([
    (new FontFace("LCD Solid", "url('assets/LCD_Solid.ttf')")).load().then((font)=>{
        document.fonts.add(font)
    }, (err)=>{
        throw {type: "font_loading_err", fontName: "LCD Solid", DOMException: err}
    }),
    (new FontFace("Conformity", "url('assets/Conformity.ttf')")).load().then((font)=>{
        document.fonts.add(font)
    }, (err)=>{
        throw {type: "font_loading_err", fontName: "Conformity", DOMException: err}
    }),
]).then(loadGame, oops)

The promises are resolved, but not loaded. Chrome and Firefox only load them when I use fillText(), originating some frames with the default serif font, until the font loads, in Chrome.

According to the spec the promises should only be resolved when the fonts are loaded, but it also allows for lazy loading.

Is there any way to avoid lazy-loading the fonts and force the browser to load them at that moment?


Solution

  • I think FontFaceObserver could work well for you here. You can still load the fonts using the JS API or the usual @font-face CSS API, and FontFaceObserver will help you trigger something else when the font has loaded.

    Here’s a full example combining your code and the multiple fonts example from the FontFaceObserver README:

    <canvas id="js-canvas"></canvas>
    <script src="https://unpkg.com/fontfaceobserver@2.1.0/fontfaceobserver.standalone.js"></script>
    <script>
    var exampleFontData = {
      'Family A': { weight: 400, src: 'url(assets/FamilyA-Regular.woff2), url(assets/FamilyA-Regular.woff)' },
      'Family B': { weight: 400, src: 'url(assets/FamilyB-Regular.otf)' },
      // Etc.
    }
    
    var observers = []
    var fonts = []
    
    // Make one Observer along with each font
    Object.keys(exampleFontData).forEach(function(family) {
      var data = exampleFontData[family]
      var obs = new FontFaceObserver(family, data)
      var font = new FontFace(family, data.src)
    
      observers.push(obs.load())
      fonts.push(
        font
          .load()
          .then((f) => {
            document.fonts.add(f)
          }, (err) => {
            throw {type: "font_loading_err", fontName: family, DOMException: err}
          })
      )
    })
    
    Promise.all(fonts)
    .then(() => {
      console.log('Use the canvas')
      var canvas = document.getElementById('js-canvas')
      canvas.width = 750
      canvas.height = 500
      var ctx = canvas.getContext('2d')
    
      ctx.font = '36px "Family A"'
      ctx.fillText('HELLO USING FONT', 20, 50)
    
      ctx.font = '36px "Family B"'
      ctx.fillText('Hello using font', 20, 150)
    }, (err) => {
      console.error(err)
    })
    </script>
    

    Result of using the fonts on the canvas.

    Hope that’s helpful!