Как получить уведомление после загрузки веб-шрифта
Google Web Fonts API предлагает способ определения функций обратного вызова, которые будут выполняться, если шрифт завершил загрузку или не мог быть загружен и т. Д. Есть ли способ добиться чего-то подобного с помощью веб-шрифтов CSS3 (@font-face)?
7 ответов
Обновление 2015
Chrome 35+ и Firefox 41+ реализуют API загрузки шрифтов CSS ( MDN, W3C). Вызов document.fonts
чтобы получить объект FontFaceSet, который имеет несколько полезных API для определения статуса загрузки шрифтов:
check(fontSpec)
- возвращает все ли шрифты в данном списке шрифтов были загружены и доступны.fontSpec
использует сокращенный синтаксис CSS для шрифтов.
Пример:document.fonts.check('bold 16px Roboto'); // true or false
document.fonts.ready
- возвращает Обещание, указывающее, что загрузка шрифта и операции макета выполнены.
Пример:document.fonts.ready.then(function () { /*... all fonts loaded...*/ });
Вот фрагмент, показывающий эти API, а также document.fonts.onloadingdone
, который предлагает дополнительную информацию о шрифте лица.
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 не поддерживает API. Посмотрите на доступные полифилы или библиотеки поддержки, если вам нужна поддержка IE:
- Web Font Loader - разработан Google и Adobe
- FontFaceOnload - более легкий, похожий подход к Web Font Loader
- FontLoader - полифилл
Протестировано в 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]);
}
};
Используйте это как:
waitForWebfonts(['MyFont1', 'MyFont2'], function() {
// Will be called as soon as ALL specified fonts are available
});
Библиотека JS, используемая Google Web Fonts API (и Typekit), может использоваться без службы: WebFont Loader.
Он определяет обратные вызовы для того, что вы просите, и многое другое.
Обновление 2017
Библиотека JS FontFaceObserver, безусловно, является лучшим, самым легким, кросс-браузерным решением на 2017 год. Она также предоставляет технологию Promise-based. .load()
интерфейс.
Я создал два метода для проверки определенного шрифта. Первый метод является лучшим, поскольку он использует интерфейс «шрифты» непосредственно с методом «проверка». Второй метод не так хорош, но все же функционален, поскольку он обнаруживает разницу непосредственно в DOM, сравнивая размер текста со шрифтом по умолчанию с текстом с новым шрифтом. Возможно, хотя и редко, шрифты будут настолько близки по размеру, что событие не сработает, но я думаю, что это крайне маловероятно. Если это произойдет, вы можете добавить еще один интервал, чтобы проверить разницу между шрифтом с засечками.
(Хотя это чистый JavaScript, он работает с React)
МЕТОД 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)
})
}
МЕТОД 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
поле ненадежно в Safari. Я нашел единственный надежный кроссбраузерный способ (современные браузеры) многократно проверятьdocument.fonts.status === 'loaded'
. Вот пример с экспоненциальным откатом:
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
Событие window.load сработает, когда все загрузится - это должно включать шрифты, так что вы можете использовать это как обратный вызов. Однако я не думаю, что вы должны это вы решили использовать загрузчик веб-шрифтов в качестве
В дополнение к опциям google, typekit, ascender и monotype, есть также пользовательский модуль, который может загружать таблицу стилей от любого поставщика веб-шрифтов.
WebFontConfig = {custom: {family: ['OneFont', 'AnotherFont'], URL: [ 'http://myotherwebfontprovider.com/stylesheet1.css', 'http://yetanotherwebfontprovider.com/stylesheet2.css' ] } };
Библиотека отправляет одни и те же события независимо от того, какого провайдера вы указали.