如何在网页字体加载后收到通知

How to be notified once a web font has loaded

本文关键字:通知 加载 字体 网页      更新时间:2023-09-26

Google的Web Fonts API提供了一种方法来定义当字体加载完成或无法加载时执行的回调函数。是否有一种方法来实现类似的使用CSS3网页字体(@font-face)?

2015更新

Chrome 35+和Firefox 41+实现CSS字体加载API (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字体加载器
  • FontLoader - polyfill

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

Google Web Fonts API(和Typekit)使用的JS库可以在没有WebFont Loader服务的情况下使用。

它定义了您所请求的回调,以及更多。

2017更新

截至2017年,JS库FontFaceObserver绝对是最好的,最轻量级的跨浏览器解决方案。它还公开了基于promise的.load()接口。

我创建了两个方法来检查特定的字体。第一种方法是最好的,因为它直接使用'fonts'接口和'check'方法。第二种方法没有那么好,但仍然有效,因为它通过比较使用默认字体的文本和使用新字体的文本的大小来直接检测DOM中的差异。虽然很罕见,但有可能字体大小非常接近而不会触发事件,但我认为这种可能性很小。如果发生这种情况,您可以添加另一个span来检查衬线字体之间的差异。

(虽然它是纯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)
    })
}

窗口。加载事件将在所有内容加载完毕时触发-应该包括字体所以你可以用它作为回叫。但是如果你决定使用web字体加载器

,我认为你不必这样做

除了google, typekit,Ascender和monotype选项,在那里也是一个自定义模块,可以加载任何网页字体的样式表提供者。

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

无论您指定哪个提供程序,库都发送相同的事件。