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

10

88

I'm using Font-Awesome, but while the font files are not loaded, the icons appear with .

So, I want these icons to have display:none while files are not loaded.

@font-face {
  font-family: "FontAwesome";
  src: url('../font/fontawesome-webfont.eot');
  src: url('../font/fontawesome-webfont.eot?#iefix') format('eot'), url('../font/fontawesome-webfont.woff') format('woff'), url('../font/fontawesome-webfont.ttf') format('truetype'), url('../font/fontawesome-webfont.svg#FontAwesome') format('svg');
  font-weight: normal;
  font-style: normal;
}

How do I know that these files have been loaded and I'm finally able to show the icons?

Edit: I'm not talking when the page is loaded (onload), because the font could be loaded before the whole page.

Bamboozle answered 7/9, 2012 at 5:40 Comment(4)
Hopefully we can have native font events soon blog.typekit.com/2013/02/05/more-reliable-font-eventsClovah
Duplicate of How to be notified once a web font has loadedHygienist
This question is a duplicate, and I've posted a 2015 update answer on the original question.Hygienist
There's a solution which uses scroll detection at smnh.me/web-font-loading-detection-without-timersClovah
M
45

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

Marauding answered 7/9, 2012 at 10:31 Comment(4)
Comparing font lengths... Is this what WebFont Loader (see the other answer) is doing as well?Clovah
And in any case, comparing character lengths will not work for many fonts because many "copy" fonts are designed to have equal lengths. For example, Arial, Helvetica, and Liberation Sans all have identical character widths for all characters. Also see en.wikipedia.org/wiki/Arial . So far it seems like pixel-for-pixel checking using canvas might be the only fool-proof option.....Clovah
I needed to use this to fix an issue I was having with iScroll calculating the sizes of elements wrong before the fonts loaded. But I am not using jQuery, so have made a vanilla js version: github.com/keithswright/vanilla-fontspy seems to be working for me.Bayard
@Clovah - note that it's only your chosen test and your chosen loading font that need to have different lengths. So, unless Comic Sans has a lot of fonts out there that it has identical character widths to, this should still work in most cases.Rattan
T
21

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.)

Toscanini answered 7/9, 2012 at 5:55 Comment(0)
H
11

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.onloadingerror = () => {
  // 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'))
Humidistat answered 3/3, 2020 at 21:59 Comment(4)
thank you. it totally works! But I need to trigger the font loading by placing a <span> element using that font somewhere.Eschar
not supported in IE11, which unfortunately still comes bundled with Windows ServerRaynold
@GuyPassy, I don't know what is the IE11 !!Humidistat
@Humidistat I wish I could one day be so luckyRaynold
Z
9

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.

Zepeda answered 7/7, 2014 at 19:41 Comment(1)
I am facing the same problem when loading font-awesome in KonvaJSBrindle
D
3

Here's another way of knowing if a @font-face has already been loaded without having to use timers at all: utilize a "scroll" event to receive an instantaneous event when the size of a carefully crafted element is changed.

I wrote a blog post about how it's done and have published the library on Github.

Die answered 8/4, 2013 at 15:42 Comment(0)
B
1

Try something like

$(window).bind("load", function() {
       $('#text').addClass('shown');
});

and then do

#text {visibility: hidden;}
#text.shown {visibility: visible;}

The load event should fire after the fonts are loaded.

Baseman answered 7/9, 2012 at 5:50 Comment(3)
This is the same of $(function(){...}) that runs when the entire page is loaded.Bamboozle
it is not the same. hayk.mart's example will trigger when the DOM (HTML) AND assets within the page (CSS, JS, images, frames) are finished loading. Your example when only the DOM has finished loading.Scroll
Curious why this answer is downvoted, a quick search shows it's the right approach, e.g. eager.io/blog/how-to-decide-when-your-code-should-runImpenitent
C
1

alternatively, you could add font-display: block to your @font-face declaration.

this instructs browsers to render the fallback font as invisible until your font is loaded, no need for display: none or any javascript load font detection

Counterespionage answered 13/4, 2021 at 23:49 Comment(0)
B
0

Solution for Typescript, Angular.

If you are working with Angular, you can use this module in order to do a font check.

// document.fonts.check extension
import type {} from 'css-font-loading-module';

ngOnInit() {
  this.onFontLoad();
}

public onFontLoad() {
  let myTimer = setInterval(() => {
    if (document.fonts.check('14px MyFont')) {
      console.log('Font is loaded!');
      clearInterval(myTimer);
    } else {
      console.log('Font is loading');
    }
  }, 1);
}

Also, some fonts are extremely heavy. Therefore, you can add a loading screen while the font is loading and remove the loading screen when the font is loaded. I believe this is a better approach rather than changing your CSS class to display: none, merely because it might take 3-4+ seconds to download some fonts if the user has slow internet.

Bornstein answered 29/11, 2021 at 6:23 Comment(0)
C
-1

This is an alternate approach that will at least ensure that font-awesome is loaded, NOT a complete solution to the OP. Original code found in the wordpress forums here https://wordpress.stackexchange.com/a/165358/40636.

It's agnostic and will work with any font style resource like font-awesome where a font-family can be checked. With a little more thought I bet this could be applied to much more...

<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<script>
    (function($){
        var faSpan = $('<span class="fa" style="display:none"></span>').appendTo('body');
        if (faSpan .css('fontFamily') !== 'FontAwesome' ) {
            // Fallback Link
            $('head').append('<link href="/css/font-awesome.min.css" rel="stylesheet">');
        }
        faSpan.remove();
    })(jQuery);
</script>
Catheterize answered 19/2, 2015 at 19:8 Comment(2)
This is a fallback in case Font-Awesome doesn't load (or if it loads too slowly!), but doesn't notify when the font has finished loading.Hygienist
@DanDascalescu I've updated the answer to more clearly indicate that this is an alternate approach that only ensures that the font-awesome library is loaded, and not a complete solution. Hopefully that clears it up a bit, as I've earned some downvotes from the previous iteration.Catheterize
A
-3

Use the below code:

<!DOCTYPE HTML>
<html>
    <head>
    </head>

<body>
<canvas id="canvasFont" width="40px" height="40px" style="position: absolute; display: none;"></canvas>

<script>
function IsLoadedFonts()
    {
        var Args = arguments;
        var obj = document.getElementById('canvasFont');
        var ctx = obj.getContext("2d");
        var baseFont = (/chrome/i.test(navigator.userAgent))?'tims new roman':'arial';
         //................
          function getImg(fon)
          { 
            ctx.clearRect(0, 0, (obj).width, (obj).height);
            ctx.fillStyle = 'rgba(0,0,0,1.0)';
            ctx.fillRect( 0, 0, 40, 40 );
            ctx.font = '20px '+ fon;
            ctx.textBaseline = "top";
            ctx.fillStyle = 'rgba(255,255,255,1.0)';
            ctx.fillText( '\u0630', 18, 5 );
            return ctx.getImageData( 0, 0, 40, 40 );
          };
        //..............
          for(var i1=0; i1<Args.length; i1++)
          {
            data1 = getImg(Args[i1]);
            data2 = getImg(baseFont);
            var isLoaded = false;
            //...........
            for (var i=0; i<data1.data.length; i++)
            {
                if(data1.data[i] != data2.data[i])
                    {isLoaded = true; break;}
            }
            //..........
            if(!isLoaded)
                    return false;
         }
         return true;
    };

     setTimeout(function(){alert(IsLoadedFonts('myfont'));},100);
   </script>
   </body>

Can check many fonts:

setTimeout(function(){alert(IsLoadedFonts('font1','font2','font3'));},100);

The below code works in opera only but is easy:

if(!document.defaultView.getComputedStyle(document.getElementById('mydiv'))['fontFamily'].match(/myfont/i))
          alert("font do not loaded ");
Andesine answered 21/5, 2015 at 9:35 Comment(1)
Same "render to canvas" idea that Leeft posted a year earlier.Hygienist

© 2022 - 2024 — McMap. All rights reserved.