How to know if a font (@font-face) has already been loaded?

Solution 1:

Now on GitHub: https://github.com/patrickmarabeas/jQuery-FontSpy.js

Essentially the method works by comparing the width of a string in two different fonts. We are using Comic Sans as the font to test against, because it is the most different of the web safe fonts and hopefully different enough to any custom font you will be using. Additionally we are using a very large font-size so even small differences will be apparent. When the width of the Comic Sans string has been calculated, the font-family is changed to your custom font, with a fallback to Comic Sans. When checked, if the string element width is the same, the fallback font of Comic Sans is still in use. If not, your font should be operational.

I rewrote the method of font load detection into a jQuery plugin designed to give the developer the ability to style elements based upon whether the font has been loaded or not. A fail safe timer has been added so the user isn’t left without content if the custom font fails to load. That’s just bad usability.

I have also added greater control over what happens during font loading and on fail with the inclusion of classes addition and removal. You can now do whatever you like to the font. I would only recommend modifying the fonts size, line spacing, etc to get your fall back font as close to the custom as possible so your layout stays intact, and users get an expected experience.

Here's a demo: http://patrickmarabeas.github.io/jQuery-FontSpy.js

Throw the following into a .js file and reference it.

(function($) {

    $.fontSpy = function( element, conf ) {
        var $element = $(element);
        var defaults = {
            font: $element.css("font-family"),
            onLoad: '',
            onFail: '',
            testFont: 'Comic Sans MS',
            testString: 'QW@HhsXJ',
            delay: 50,
            timeOut: 2500
        };
        var config = $.extend( defaults, conf );
        var tester = document.createElement('span');
            tester.style.position = 'absolute';
            tester.style.top = '-9999px';
            tester.style.left = '-9999px';
            tester.style.visibility = 'hidden';
            tester.style.fontFamily = config.testFont;
            tester.style.fontSize = '250px';
            tester.innerHTML = config.testString;
        document.body.appendChild(tester);
        var fallbackFontWidth = tester.offsetWidth;
        tester.style.fontFamily = config.font + ',' + config.testFont;
        function checkFont() {
            var loadedFontWidth = tester.offsetWidth;
            if (fallbackFontWidth === loadedFontWidth){
                if(config.timeOut < 0) {
                    $element.removeClass(config.onLoad);
                    $element.addClass(config.onFail);
                    console.log('failure');
                }
                else {
                    $element.addClass(config.onLoad);
                    setTimeout(checkFont, config.delay);
                    config.timeOut = config.timeOut - config.delay;
                }
            }
            else {
                $element.removeClass(config.onLoad);
            }
        }
        checkFont();
    };

    $.fn.fontSpy = function(config) {
        return this.each(function() {
            if (undefined == $(this).data('fontSpy')) {
                var plugin = new $.fontSpy(this, config);
                $(this).data('fontSpy', plugin);
            }
        });
    };

})(jQuery);

Apply it to your project

.bannerTextChecked {
        font-family: "Lobster";
        /* don't specify fallback font here, do this in onFail class */
}

$(document).ready(function() {

    $('.bannerTextChecked').fontSpy({
        onLoad: 'hideMe',
        onFail: 'fontFail anotherClass'
    });

});

Remove that FOUC!

.hideMe {
    visibility: hidden !important;
}

.fontFail {
    visibility: visible !important;
    /* fall back font */
    /* necessary styling so fallback font doesn't break your layout */
}

EDIT: FontAwesome compatibility removed as it didn't work properly and ran into issues with different versions. A hacky fix can be found here: https://github.com/patrickmarabeas/jQuery-FontFaceSpy.js/issues/1

Solution 2:

Try WebFont Loader (github repo), developed by Google and Typekit.

This example first displays the text in the default serif font; then after the fonts have loaded it displays the text in the specified font. (This code reproduces Firefox's default behavior in all other modern browsers.)

Solution 3:

Here is a different approach to the solutions from others.

I'm using FontAwesome 4.1.0 to build WebGL textures. That gave me the idea to use a tiny canvas to render a fa-square to, then check a pixel in that canvas to test whether it has loaded:

function waitForFontAwesome( callback ) {
   var retries = 5;

   var checkReady = function() {
      var canvas, context;
      retries -= 1;
      canvas = document.createElement('canvas');
      canvas.width = 20;
      canvas.height = 20;
      context = canvas.getContext('2d');
      context.fillStyle = 'rgba(0,0,0,1.0)';
      context.fillRect( 0, 0, 20, 20 );
      context.font = '16pt FontAwesome';
      context.textAlign = 'center';
      context.fillStyle = 'rgba(255,255,255,1.0)';
      context.fillText( '\uf0c8', 10, 18 );
      var data = context.getImageData( 2, 10, 1, 1 ).data;
      if ( data[0] !== 255 && data[1] !== 255 && data[2] !== 255 ) {
         console.log( "FontAwesome is not yet available, retrying ..." );
         if ( retries > 0 ) {
            setTimeout( checkReady, 200 );
         }
      } else {
         console.log( "FontAwesome is loaded" );
         if ( typeof callback === 'function' ) {
            callback();
         }
      }
   }

   checkReady();
};

As it uses a canvas it requires a fairly modern browser, but it might work on IE8 as well with the polyfill.

Solution 4:

Actually, there is a good way to understand all fonts begin to download or loaded completely or not and fall into some errors, but it is not just for a specific font, pay attention to the following code:

document.fonts.onloading = () => {
  // do someting when fonts begin to download
};
document.fonts.onloadingdone = () => {
  // do someting when fonts are loaded completely
};
document.fonts.onloading = () => {
  // do someting when fonts fall into some error
};

And also there is an option that returns Promise and it could handle with .then function:

document.fonts.ready
 .then(() => console.log('do someting at the final with each status'))