Search code examples
javascriptmobilefontsfabricjs

Fabricjs Impact font not loading on mobile devices


I have a demo here: https://codepen.io/Jsbbvk/pen/KKXNPYO

var canvas = new fabric.Canvas("canvas");

const text = new fabric.IText("sample text")
text.set({
  fontFamily: "Impact",
  left: (this.canvas.width - text.width) / 2,
  top: (this.canvas.height - text.height) / 2,
})
canvas.add(text)
canvas.requestRenderAll()

If you visit this codepen on a mobile device, you'll see that the Impact font doesn't load. However, visiting this codepen on a desktop will work. Is there a fix to this?


Solution

  • Impact is not preinstalled on android devices

    Actually, the term "web safe fonts" is just plain wrong! (see also: "Impact" font not working on mobile Chrome

    Workaround use a async font loader to check the availability

    1. Check if fonts are available via document.fonts.check()
    2. If necessary: load the font via asynchronous helper function loadFonts(fontsToLoad) (based on FontFace.load() method)
    3. You also need to init fabric.js after fonts are loaded:

    JS:

    async function initFabric() {
      var canvas = new fabric.Canvas("canvas");
      const text = new fabric.IText("sample text");
      let canvasContainer = document.querySelector(".canvas-container");
    
      //load fonts if necessary
      let fontsToLoad = checkFontAvailability(fonts);
      await loadFonts(fontsToLoad);
    
      text.set({
        fontFamily: "Impact",
        left: (this.canvas.width - text.width) / 2,
        top: (this.canvas.height - text.height) / 2
      });
      canvas.add(text);
      canvas.requestRenderAll();
    }
    

    Working example

    /**
    * fonts to check
    */
    let fonts = [{
        'font-family': 'Arial',
        'font-style': 'normal',
        'font-weight': 400,
        'src': 'https://fonts.gstatic.com/s/anton/v23/1Ptgg87LROyAm3Kz-C8.woff2'
      },
      {
        'font-family': 'Times New Roman',
        'font-style': 'normal',
        'font-weight': 400,
        'src': 'https://fonts.gstatic.com/s/anton/v23/1Ptgg87LROyAm3Kz-C8.woff2'
      },
      {
        // Inter as replacement
        'font-family': 'Helvetica',
        'font-style': 'normal',
        'font-weight': 400,
        'src': 'https://fonts.gstatic.com/s/inter/v12/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7.woff2'
      },
      {
        // Anton  as replacement
        'font-family': 'Impact',
        'font-style': 'normal',
        'font-weight': 400,
        'src': 'https://fonts.gstatic.com/s/anton/v23/1Ptgg87LROyAm3Kz-C8.woff2'
      },
    ]
    
    
    /**
    * init fabric after font check
    */
    initFabric();
    
    async function initFabric() {
      var canvas = new fabric.Canvas("canvas");
      const text = new fabric.IText("sample text");
      let canvasContainer = document.querySelector('.canvas-container');
    
      //load fonts if necessary
      let fontsToLoad = checkFontAvailability(fonts);
      await loadFonts(fontsToLoad);
    
      text.set({
        fontFamily: "Impact",
        left: (this.canvas.width - text.width) / 2,
        top: (this.canvas.height - text.height) / 2,
      })
      canvas.add(text)
      canvas.requestRenderAll()
    
      //add buttons
      fonts.forEach(function(font) {
        let fontFamily = font['font-family']
        let btn = document.createElement('button')
        btn.setAttribute('type', 'button')
        btn.classList.add('btn-font');
        btn.textContent = fontFamily
        canvasContainer.parentNode.insertBefore(btn, canvasContainer);
    
        btn.addEventListener("click", () => {
          text.fontFamily = fontFamily
          canvas.renderAll()
        })
      })
    }
    
    
    function checkFontAvailability(fonts) {
      let info = [];
      let fontsToLoad = [];
      if (fonts.length) {
        fonts.forEach(function(font) {
          let fontFamily = font['font-family'];
          let fontApplied = document.fonts.check(`12px ${fontFamily}`);
          if (!fontApplied) {
            fontsToLoad.push(font)
          }
        })
      }
      return fontsToLoad;
    }
    
    async function loadFonts(fontsToLoad) {
      if (fontsToLoad.length) {
        for (let i = 0; i < fontsToLoad.length; i++) {
          let fontProps = fontsToLoad[i];
          let fontFamily = fontProps['font-family'];
          let fontWeight = fontProps['font-weight'];
          let fontStyle = fontProps['font-style'];
          let fontUrl = Array.isArray(fontProps['src']) ? fontProps['src'][0][0] : fontProps[
            'src'];
          if (fontUrl.indexOf('url(') === -1) {
            fontUrl = 'url(' + fontUrl + ')';
          }
          let fontFormat = fontProps['src'][0][1] ? fontProps['src'][1] : '';
          const font = new FontFace(fontFamily, fontUrl);
          font.weight = fontWeight;
          font.style = fontStyle;
          await font.load();
          document.fonts.add(font);
          console.log(fontFamily, 'loaded')
    
          // apply font styles to invisible elements
          let fontDOMEl = document.createElement('div');
          fontDOMEl.textContent = '';
          document.body.appendChild(fontDOMEl);
          fontDOMEl.setAttribute(
            "style",
            `position:fixed; height:0; width:0; overflow:hidden; font-family:${fontFamily}; font-weight:${fontWeight}; font-style:${fontStyle}`
          );
        }
      }
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.3.0/fabric.min.js"></script>
    <canvas width="300" height="300" id="canvas"></canvas>

    loadFonts() will append invisible elements to your DOM with the desired font-families applied.
    This will ensure the loaded fonts are also available for canvas elements.