Как получить уведомление после загрузки веб-шрифта

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:

Протестировано в 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' ] } };

Библиотека отправляет одни и те же события независимо от того, какого провайдера вы указали.

Другие вопросы по тегам