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

发布于 2024-11-01 23:34:02 字数 105 浏览 5 评论 0原文

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

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)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

空城旧梦 2024-11-08 23:34:02

2015 更新

Chrome 35+ 和 Firefox 41+ 实现 CSS 字体加载 API (MDNW3C)。调用 document.fonts 获取 FontFaceSet 对象,它有一些有用的 API 用于检测字体的加载状态:

  • check(fontSpec) - 返回给定字体列表中的所有字体是否已加载且可用。 fontSpec 使用 CSS 字体速记语法.
    示例:document.fonts.check('bold 16px Roboto'); // true 或 false
  • document.fonts .ready - 返回 Promise 表示字体加载和布局操作已完成。
    示例:document.fonts.ready.then(function () { /*...所有字体已加载。 ..*/ });

这是显示这些 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,请查看可用的 polyfill 或支持库:

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:

弃爱 2024-11-08 23:34:02

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

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
});
十年九夏 2024-11-08 23:34:02

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

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

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.

冷心人i 2024-11-08 23:34:02

2017更新

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

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.

追星践月 2024-11-08 23:34:02

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

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
阪姬 2024-11-08 23:34:02

我创建了两种方法来检查特定字体。第一种方法是最好的方法,因为它直接使用“字体”接口和“检查”方法。第二种方法虽然不太好,但仍然有效,因为它通过将默认字体的文本大小与新字体的文本大小进行比较来直接在 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)
    })
}

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)
    })
}
长亭外,古道边 2024-11-08 23:34:02

当所有内容都已加载时, window.load 事件将触发 - 其中应该包括字体
所以你可以用它作为回调。不过,我认为您不必这样做,因为您决定使用网络字体加载器作为

除了google、typekit之外,
ascender 和 monotype 选项,有
也是一个自定义模块,可以加载
任何网络字体的样式表
提供商。

WebFontConfig = { 自定义:{
家庭:['OneFont','AnotherFont'],
urls: ['http://myotherwebfontprovider.com/stylesheet1.css',
'http://yetanotherwebfontprovider.com/stylesheet2.css'
] } };

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文