Drawing text to HTML5 with @fontface does not work at the first time

Drawing text in an HTML5 canvas with a typeface loaded via @font-face often fails to display correctly on the first render. This occurs because the browser has not yet fully loaded the custom font from the network, causing it to fall back to a default system font instead.

The key issue is timing − the canvas attempts to draw text before the custom font has finished downloading and becomes available to the rendering engine.

Syntax

Following is the basic syntax for defining a custom font with @font-face −

@font-face {
   font-family: 'CustomFont';
   src: url('font.woff2') format('woff2'),
        url('font.woff') format('woff');
}

To use the font in canvas drawing −

context.font = '16px CustomFont';
context.fillText('Hello World', x, y);

Preloading Fonts with Hidden Elements

One simple solution is to preload the font by creating a hidden element that uses the custom font. This forces the browser to download and prepare the font before canvas rendering begins.

Example

<!DOCTYPE html>
<html>
<head>
   <title>Font Preloading with Hidden Div</title>
   <style>
      @font-face {
         font-family: 'PressStart';
         src: url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
      }
      .font-preloader {
         font-family: 'PressStart', monospace;
         position: absolute;
         left: -9999px;
         visibility: hidden;
      }
   </style>
</head>
<body style="font-family: Arial, sans-serif; padding: 10px;">
   <div class="font-preloader">Preload font</div>
   <canvas id="myCanvas" width="400" height="150" style="border: 1px solid #ccc;"></canvas>
   
   <script>
      setTimeout(function() {
         var canvas = document.getElementById('myCanvas');
         var ctx = canvas.getContext('2d');
         ctx.font = '20px PressStart, monospace';
         ctx.fillStyle = '#333';
         ctx.fillText('Custom Font Text', 50, 80);
      }, 100);
   </script>
</body>
</html>

The hidden div forces the browser to load the PressStart font, making it available when the canvas draws text.

Using the FontFace API

The modern approach uses the FontFace API to programmatically load fonts and wait for them to be ready before drawing to the canvas.

Example

<!DOCTYPE html>
<html>
<head>
   <title>FontFace API Example</title>
</head>
<body style="font-family: Arial, sans-serif; padding: 10px;">
   <canvas id="myCanvas" width="400" height="150" style="border: 1px solid #ccc;"></canvas>
   <p id="status">Loading font...</p>
   
   <script>
      var customFont = new FontFace('CustomFont', 'url(https://fonts.gstatic.com/s/pressstart2p/v8/e3t4euO8T-267oIAQAu6jDQyK3nVivM.woff2)');
      
      customFont.load().then(function(font) {
         document.fonts.add(font);
         document.getElementById('status').textContent = 'Font loaded successfully!';
         
         var canvas = document.getElementById('myCanvas');
         var ctx = canvas.getContext('2d');
         ctx.font = '16px CustomFont';
         ctx.fillStyle = '#2c3e50';
         ctx.fillText('FontFace API Success!', 50, 80);
      }).catch(function(error) {
         document.getElementById('status').textContent = 'Font loading failed: ' + error;
      });
   </script>
</body>
</html>

This approach ensures the font is fully loaded before attempting to draw text, preventing fallback font issues.

Using Document.fonts.ready Promise

Another reliable method is to wait for all fonts to be loaded using the document.fonts.ready promise.

Example

<!DOCTYPE html>
<html>
<head>
   <title>Document Fonts Ready</title>
   <style>
      @font-face {
         font-family: 'MyFont';
         src: url('https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu4mxP.woff2') format('woff2');
      }
   </style>
</head>
<body style="font-family: Arial, sans-serif; padding: 10px;">
   <canvas id="myCanvas" width="400" height="150" style="border: 1px solid #ccc;"></canvas>
   <p id="status">Waiting for fonts...</p>
   
   <script>
      document.fonts.ready.then(function() {
         document.getElementById('status').textContent = 'All fonts ready!';
         
         var canvas = document.getElementById('myCanvas');
         var ctx = canvas.getContext('2d');
         ctx.font = '18px MyFont, sans-serif';
         ctx.fillStyle = '#e74c3c';
         ctx.fillText('All fonts loaded and ready!', 50, 80);
      });
   </script>
</body>
</html>

This method waits for all @font-face declarations to complete loading before proceeding with canvas operations.

Comparison of Methods

Method Pros Cons
Hidden Div Preloading Simple, works in older browsers Less reliable, adds DOM elements
FontFace API Precise control, promise-based Limited browser support (IE not supported)
document.fonts.ready Waits for all fonts, clean syntax Modern browsers only, waits for all fonts

Best Practices

To ensure reliable font loading for canvas text rendering −

  • Always wait for fonts − Never draw text immediately after page load without ensuring fonts are ready.

  • Provide fallbacks − Include fallback fonts in your font-family declaration (e.g., 'CustomFont', Arial, sans-serif).

  • Use font-display − Add font-display: swap; to @font-face rules for better loading behavior.

  • Test loading states − Always test how your canvas behaves during font loading periods.

Conclusion

Custom fonts in HTML5 canvas require proper loading management to avoid display issues. Use the FontFace API or document.fonts.ready promise for modern browsers, or implement hidden element preloading for broader compatibility. Always ensure fonts are fully loaded before rendering text to canvas.

Updated on: 2026-03-16T21:38:53+05:30

565 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements