45

I am using @font-face and I hate that Firefox shows the default font, waits to load the @font-face font, then replaces it. So the whole page flashes with the new font.

Webkit browsers just don't display the text until the font is loaded and it is a much cleaner look.

So, I am wondering if jQuery could help me to know when all data on the page is loaded, including the @font-face file, so that I can then show my text? Is there a jQuery method that tells me when everything is loaded?

Nic Hubbard
  • 41,587
  • 63
  • 251
  • 412
  • 1
    I almost posted an answer but it's so lame that I think I'll wait and see if a smart person knows a good way to do it. – Pointy Dec 08 '10 at 01:08
  • @Pointy - Haha, that is fine. – Nic Hubbard Dec 08 '10 at 01:10
  • For the record, my aborted answer was a suggestion to watch the size of some test element (possibly hidden) with a timer and wait to see when its size changes. That is, you start the timer *before* you try to load the font(s), and then when the timer routine (running every 50ms or so) sees that the size of some box has changed, it knows that the fonts must have arrived. – Pointy Dec 08 '10 at 01:14
  • 1
    Oooh, oooh, ooh -- you could load the font files with Image objects, and when the onload fires you could flip a switch that enables the CSS and also reveals the styled elements. – Pointy Dec 08 '10 at 01:15
  • ... except "load" may not fire if the response isn't an image ... – Pointy Dec 08 '10 at 01:38
  • If someone still ends up here, Option1 from this link works nice for me! https://portalzine.de/dev/options-to-detect-when-a-font-face-has-been-loaded/ – chris Jun 29 '18 at 04:59

7 Answers7

54

I use this function - tested in Safari, Chrome, Firefox, Opera, IE7, IE8, IE9:

function waitForWebfonts(fonts, callback) {
    var loadedFonts = 0;
    for(var i = 0, l = fonts.length; i < l; ++i) {
        (function(font) {
            var node = document.createElement('span');
            // Characters that vary significantly among different fonts
            node.innerHTML = 'giItT1WQy@!-/#';
            // Visible - so we can measure it - but not on the screen
            node.style.position      = 'absolute';
            node.style.left          = '-10000px';
            node.style.top           = '-10000px';
            // Large font size makes even subtle changes obvious
            node.style.fontSize      = '300px';
            // Reset any font properties
            node.style.fontFamily    = 'sans-serif';
            node.style.fontVariant   = 'normal';
            node.style.fontStyle     = 'normal';
            node.style.fontWeight    = 'normal';
            node.style.letterSpacing = '0';
            document.body.appendChild(node);

            // Remember width with no applied web font
            var width = node.offsetWidth;

            node.style.fontFamily = font + ', sans-serif';

            var interval;
            function checkFont() {
                // Compare current width with original width
                if(node && node.offsetWidth != width) {
                    ++loadedFonts;
                    node.parentNode.removeChild(node);
                    node = null;
                }

                // If all fonts have been loaded
                if(loadedFonts >= fonts.length) {
                    if(interval) {
                        clearInterval(interval);
                    }
                    if(loadedFonts == fonts.length) {
                        callback();
                        return true;
                    }
                }
            };

            if(!checkFont()) {
                interval = setInterval(checkFont, 50);
            }
        })(fonts[i]);
    }
};

Use it like:

waitForWebfonts(['MyFont1', 'MyFont2'], function() {
    // Will be called as soon as ALL specified fonts are available
});
Ivan Castellanos
  • 8,041
  • 1
  • 47
  • 42
Thomas Bachem
  • 1,545
  • 1
  • 16
  • 10
  • 3
    Excellent unbloated solution. Has anyone encountered a font that didn't cause the size to change (thus locking! Maybe a max_time_to_wait would be prudent..) ? – T4NK3R Feb 06 '14 at 08:38
  • 3
    Seems a little unfortunate that you create a `setInterval()` for each font. Would rather have a single `setInterval()` that checks all fonts that still need checking. – jfriend00 Feb 26 '14 at 15:41
  • 2
    Does not work in Chrome 39. When the callback is called, the font is loaded, but there are many elements on my page which are still rendered in the fallback font (Arial). – Jonas Sourlier Dec 02 '14 at 09:54
  • 1
    I found a serious bug with this solution, basically sometimes doesn't work because as soon as you change the `fontFamily` for the not-yet-loaded-font it starts using an inherited font (the span, due any CSS rule) so it says it has changed, so it executes the callback before the web font is loaded. I just submitted a fix. – Ivan Castellanos Oct 16 '16 at 00:56
  • 1
    This code is problematic; It runs the callback multiple times, because you make intervals PER font - but they only get cleared when ALL the fonts have loaded. Moving the `if(interval) { clearInterval(interval);}` to after `++loadedFonts;` fixes the problem. – 1owk3y May 16 '19 at 02:27
27

Ok, it was pretty easy. Basically I just set my text to:

a.main {visibility: hidden;}

and then add:

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

Then make sure that the following is also in my css file:

a.main.shown {visibility: visible;}
Nic Hubbard
  • 41,587
  • 63
  • 251
  • 412
  • Yes the load event is supposed to fire after all resources for the page have been loaded, that includes the font files. – Ruan Mendes Dec 08 '10 at 02:03
  • 1
    @Chris - Are we really that worried these days about users having js turned off? Are you living in the 1990's? My target users won't have js turned off. – Nic Hubbard Dec 08 '10 at 16:57
  • 3
    on my own sites I don't care if users with JavaScript turned off get lots of functionality missing, but if you have it causing crucial parts of the page just vanishing for no good reason - that's a cause for concern in my opinion. – Chris Morgan Dec 08 '10 at 22:53
  • 10
    Maybe use this this if it's an issue ;-) – PandaWood Dec 14 '10 at 03:08
  • 24
    No meanness needed, just use: – zachleat Nov 27 '11 at 17:41
  • 41
    load event waits for font files in Firefox, but not WebKit. So this is not reliable. – Paul Irish Jan 31 '12 at 01:23
  • this is a much simpler solution, works across browsers. just tried it – Tomi Seus Sep 04 '12 at 08:48
  • `$(document).ready(function() { });` always works for me in all browsers. – Jared Jul 04 '13 at 01:49
18

You should't use $(window).bind('load') - that will wait for the whole page to load (which maybe is what you want), and not just the font. If you want to control the loading process of @font-faces use WebFont Loader, developed by Google and Typekit.

You can use it with Google Font API, typekit and your own webfont provider - you (although I never tried it myself as I'm a Typekit User.

Read about it here: http://code.google.com/apis/webfonts/docs/webfont_loader.html and here: http://blog.typekit.com/2010/05/19/typekit-and-google/

jgradim
  • 2,851
  • 1
  • 16
  • 8
4

I use google web fonts (Crete Round Regular and Open Sans Regular with Bold)

You can use either this :

var fonts = $.Deferred();
WebFontConfig = { google: { families: [ 'Crete+Round::latin', 'Open+Sans:400,700:latin' ] } , active : function() { fonts.resolve(); } };
(function() {
    var wf = document.createElement('script');
    wf.src = ('https:' == document.location.protocol ? 'https' : 'http') + '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
    wf.type = 'text/javascript';
    wf.async = 'true';
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(wf, s);
})();
fonts.done(function(){ alert('fonts'); });

or this :

WebFontConfig = { google: { families: [ 'Crete+Round::latin', 'Open+Sans:400,700:latin' ] } , active : function() { alert('fonts'); } };
(function() {
    var wf = document.createElement('script');
    wf.src = ('https:' == document.location.protocol ? 'https' : 'http') + '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
    wf.type = 'text/javascript';
    wf.async = 'true';
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(wf, s);
})();

Note that in the first option i used jQuery Deferred Object.

bool.dev
  • 17,508
  • 5
  • 69
  • 93
1

Perhaps..

Create a z-index: -10 div and fill it with a lot of text (with a 'normal' font). At document.ready() or another event:

var originalnumber = $( div ).width() + $( div ).height() + $( div ).offset().top + $( div ).offset().left;

$( div ).css( 'font-family', 'MyPrettyWebfont' );

var interval = setInterval( function() {
    var number = $( div ).width() + $( div ).height() + $( div ).offset().top + $( div ).offset().left;

    if ( number !== originalnumber ) {
        // webfont is loaded and applied!
        clearInterval( interval );
    }
}, 10 );
Sven Spruijt
  • 429
  • 5
  • 4
0

I got the same problem. And somehow i can't get Google webfont loader to work with ttf font (especially chinese fonts) or send a jquery response when font is loaded.

So I came up with this a more basic solution, useful when changing font dynamically after page is loaded. it shows when the font face is properly loaded.

First i create a dummy div and then fill it with 'abcd' and then give it a font size 40, record the width of the div. When the 'change font' event is invoked via a button or anything, it should track the dummy div width changes. Width changes would represent that the font has change.

HTML CODE

<style>
    @font-face {
     font-family: 'font1';
     src:url('somefont.ttf')  format('truetype');
     }
</style>
<div id="dummy" style="border:1px solid #000; display:inline-block">abdc</div>

<!--simple button to invoke the fontchange-->
<input type="button" onclick="javascript:changefont('font1')" value="change the font"/>

JQuery

//create a variable to track initial width of default font
var trackwidth

//when change font is invoke
function changefont(newfont)
{
   //reset dummy font style
    $('#dummy').css('font-family','initial !important;');
    $('#dummy').css({'font-size':'40px'});
    $('#dummy').html('abcd');

    //ensure that trackwidth is only recorded once of the default font
    if(trackwidth==null)
    {
       trackwidth=( $('#dummy').width());
    }

    //apply new font
    $('#dummy').css('font-family',newfont);
    checkwidth()
}

function checkwidth()
{
   //check if div width changed
   if($('#dummy').width() == trackwidth)
    {
        //if div never change, detect again every 500 milisecs
        setTimeout(checkwidth, 500);
    }
    else
    {
      //do something, font is loaded!
    }
}
Tien Lu
  • 21
  • 4
-5

I've got the same problem and I'm trying with the readyfunction(), the bind() function and some others that I found, but none of them works. Finaly I found one solution, just aplying one delay before the animation is loaded... like this:

$(document).ready(function() {

setTimeout(function (){
   // The animation
},150);

}); // end ready

I know this is not the best solution, so can someone tell me one better??

Thanks!

biegleux
  • 13,179
  • 11
  • 45
  • 52
Fernando
  • 7
  • 1