Jsbbvk
Jsbbvk

Reputation: 208

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?

Upvotes: 0

Views: 567

Answers (1)

herrstrietzel
herrstrietzel

Reputation: 17353

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.

Upvotes: 2

Related Questions