如何通知一次 Web 字体已经加载

Google 的 Web Fonts API 提供了一种方法来定义在字体加载完成或无法加载等情况下执行的回调函数。有没有一种方法可以用 CSS3网页字体(@font-face)来实现类似的功能?

67988 次浏览

The JS Library used by Google Web Fonts API (and Typekit) can be used without the service: WebFont Loader.

It defines callbacks for what you ask, and many more.

The window.load event will fire when everything has loaded - that should include fonts So you could use that as the call back. However I don't think you have to is you decide to use the web font loader as

In addition to the google, typekit, ascender and monotype options, there is also a custom module that can load a stylesheet from any web-font provider.

WebFontConfig = { custom: { families: ['OneFont', 'AnotherFont'], urls: [ 'http://myotherwebfontprovider.com/stylesheet1.css', 'http://yetanotherwebfontprovider.com/stylesheet2.css' ] } };

The library sends the same events regardless of which provider you specify.

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;


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
});

2015 Update

Chrome 35+ and Firefox 41+ implement the CSS font loading API (MDN, W3C). Call document.fonts to get a FontFaceSet object, which has a few useful APIs for detecting the load status of fonts:

  • check(fontSpec) - returns whether all fonts in the given font list have been loaded and are available. The fontSpec uses the CSS shorthand syntax for fonts.
    Example: document.fonts.check('bold 16px Roboto'); // true or false
  • document.fonts.ready - returns a Promise indicating that font loading and layout operations are done.
    Example: document.fonts.ready.then(function () { /*... all fonts loaded...*/ });

Here's a snippet showing these APIs, plus document.fonts.onloadingdone, which offers extra information about the font faces.

alert('Roboto loaded? ' + document.fonts.check('1em Roboto'));  // false


document.fonts.ready.then(function () {
alert('All fonts in use by visible text have loaded.');
alert('Roboto loaded? ' + document.fonts.check('1em Roboto'));  // true
});


document.fonts.onloadingdone = function (fontFaceSetEvent) {
alert('onloadingdone we have ' + fontFaceSetEvent.fontfaces.length + ' font faces loaded');
};
<link href='https://fonts.googleapis.com/css?family=Roboto:400,700' rel='stylesheet' type='text/css'>
<p style="font-family: Roboto">
We need some text using the font, for the font to be loaded.
So far one font face was loaded.
Let's add some <strong>strong</strong> text to trigger loading the second one,
with weight: 700.
</p>

IE 11 doesn't support the API. Look at available polyfills or support libraries if you need to support IE:

2017 Update

The JS library FontFaceObserver is definitely the best, most lightweight, cross-browser solution as of 2017. It also exposes a Promise-based .load() interface.