Google's Web Fonts API offers a way to define callback functions to be executed if a font has finished loading, or couldn't be loaded etc. Is there a way to achieve something similar using CSS3 web fonts (@font-face)?
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. ThefontSpec
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:
- Web Font Loader - developed by Google and Adobe
- FontFaceOnload - lighter, similar approach to Web Font Loader
- FontLoader - polyfill
check
always returns true
: document.fonts.check('1em NoThisReallyDoesntExist')
. Looking at the MDN docs it looks like this is very much an experimental/incomplete feature. It does seem to work correctly in Chromium. –
Ober ngAfterFontInit
in ng2 interface but there's not... –
Resist onloadingdone
. .ready
always fires immediately –
Cleocleobulus 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
});
waitForWebfonts(['"Vectora W01 56 Italic"'],...);
–
Musa sans-serif
, but if you request a serif
font: "Roboto Slab", serif
the browser will naturally switch to a default serif
font while waiting for Roboto Slab to load, triggering the measurement prematurely. Best way to fix this imo is to figure out the requested font's fallback family and use that when creating the node so that the only swap would happen once the final font has loaded. –
Shimkus waitForWebfonts(['"Roboto Slab", serif'], function() { ... });
? Specifying multiple fonts per string (also fallback fonts) was never intended. –
Spoonerism Roboto, sans-serif
, but use Roboto
instead, the browser will still add sans-serif
internally. This is even worse, because if you use, say a monospace
type of font and not tell the browser that it's monospace, the browser will assume sans-serif
(for example) until it downloads the font. You'll face the same issue. –
Shimkus sans-serif
which isn't always a case. Instead I'm checking if the font is loaded with document.fonts.check
and it works perfect in all scenarios. Replace if(node && node.offsetWidth != width) {
with if(node && document.fonts.check('16px fontName')) {
–
Loathe 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.
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.
var observe_fa = new FontFaceObserver('FontAwesome'); observe_fa.load('<SOME TEST ICON SYMBOLS GO HERE>').then(...)
. –
Syce I created two methods to check for a specific font. The first method is the best one as it uses the 'fonts' interface directly with the 'check' method. The second method is not as good, but still functional, as it detects the difference directly in the DOM by comparing the size of the text with the default font to the text with the new font. It's possible, although rare, for the fonts to be so close in size that the event wouldn't fire, but I think it's highly unlikely. If that happens, you can add another span to check the difference between the serif font as well.
(Although it is pure javascript it works with React)
METHOD 1
const fontName = "Fira Sans Condensed",
maxTime = 2500 // 2.5s
// EXAMPLE 1
fontOnload(fontName).then(() => {
console.log("success")
})
// EXAMPLE 2
fontOnload(fontName, maxTime).then(() => {
console.log("success")
}).catch(() => {
console.log("timeout")
})
async function fontOnload(fontName, maxTime = Infinity, timeInterval = 10) {
const startTime = performance.now()
return new Promise((resolve, reject) => {
setInterval(() => {
const currentTime = performance.now(),
elapsedTime = currentTime - startTime
if (document.fonts.check("12px " + fontName)) {
resolve(true)
} else if (elapsedTime >= maxTime) {
reject(false)
}
}, timeInterval)
})
}
METHOD 2
const fontName = "Fira Sans Condensed",
maxTime = 2500 // 2.5s
// EXAMPLE 1
fontOnloadDOM(fontName).then(() => {
console.log("success")
})
// EXAMPLE 2
fontOnloadDOM(fontName, maxTime).then(() => {
console.log("success")
}).catch(() => {
console.log("timeout")
})
async function fontOnloadDOM(fontName, maxTime = Infinity, timeInterval = 10) {
return new Promise((resolve, reject) => {
const startTime = performance.now(),
abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
mainStyle = "font-size:24px!important;display:inline!important;font-family:",
body = document.body,
container = document.createElement("div"),
span1 = document.createElement("span"),
span2 = document.createElement("span")
container.classList.add("font-on-load")
container.setAttribute("style", "display:block!important;position:absolute!important;top:-9999px!important;left:-9999px!important;opacity:0!important;")
span1.setAttribute("style", mainStyle + "sans-serif!important;")
span2.setAttribute("style", mainStyle + "\"" + fontName + "\",sans-serif!important;")
span1.innerText = abc.repeat(3)
span2.innerText = abc.repeat(3)
container.append(span1, span2)
body.append(container)
const interval = setInterval(() => {
const currentTime = performance.now(),
elapsedTime = currentTime - startTime,
width1 = span1.clientWidth || span1.getBoundingClientRect().width,
width2 = span1.clientWidth || span2.getBoundingClientRect().width,
diffWidths = Math.abs(width1 - width2)
if (diffWidths > 9) {
clearInterval(interval)
resolve(true)
} else if (elapsedTime >= maxTime) {
clearInterval(interval)
reject(false)
}
}, timeInterval)
})
}
The document.fonts.ready
field is unreliable on Safari. I have found the only reliable cross-browser way (modern browsers) to be repeatedly checking for document.fonts.status === 'loaded'
. Here's an example with exponential back off:
const waitForFontsLoaded = document.fonts?.ready.then(() => {
if (document.fonts?.status === 'loaded') {
return null;
}
console.warn('Browser reported fonts ready but something is still loading...');
return new Promise((resolve) => {
let waitTimeMs = 5;
const checkFontsLoaded = () => {
if (document.fonts?.status === 'loaded') {
return resolve();
}
waitTimeMs *= 2;
return setTimeout(checkFontsLoaded, waitTimeMs);
};
setTimeout(checkFontsLoaded, 5);
});
});
await waitForFontsLoaded
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.
load
event can fire before web fonts have loaded. –
Fertilizer © 2022 - 2024 — McMap. All rights reserved.