如何知道字体(@font-face)是不是已经加载?

Posted

技术标签:

【中文标题】如何知道字体(@font-face)是不是已经加载?【英文标题】:How to know if a font (@font-face) has already been loaded?如何知道字体(@font-face)是否已经加载? 【发布时间】:2012-08-31 23:54:15 【问题描述】:

我正在使用 Font-Awesome,但在未加载字体文件时,图标显示为 。

所以,我希望这些图标在未加载文件时具有display:none

@font-face 
  font-family: "FontAwesome";
  src: url('../font/fontawesome-webfont.eot');
  src: url('../font/fontawesome-webfont.eot?#iefix') format('eot'), url('../font/fontawesome-webfont.woff') format('woff'), url('../font/fontawesome-webfont.ttf') format('truetype'), url('../font/fontawesome-webfont.svg#FontAwesome') format('svg');
  font-weight: normal;
  font-style: normal;

我怎么知道这些文件已经加载并且我终于可以显示图标了?

编辑: 我不是在页面加载时(onload)说话,因为字体可以在整个页面之前加载。

【问题讨论】:

希望我们能尽快举办原生字体活动blog.typekit.com/2013/02/05/more-reliable-font-events 重复How to be notified once a web font has loaded 这个问题是重复的,我已经在原来的问题上发布了2015 update answer。 smnh.me/web-font-loading-detection-without-timers 有一个使用滚动检测的解决方案 【参考方案1】:

现在在 GitHub 上:https://github.com/patrickmarabeas/jQuery-FontSpy.js

本质上,该方法通过比较两种不同字体的字符串的宽度来工作。我们使用 Comic Sans 作为测试字体,因为它是最不同的网络安全字体,并且希望与您将使用的任何自定义字体有足够的不同。此外,我们使用了非常大的字体大小,因此即使是很小的差异也会很明显。计算出 Comic Sans 字符串的宽度后,字体系列将更改为您的自定义字体,并回退到 Comic Sans。勾选后,如果字符串元素宽度相同,Comic Sans 的后备字体仍在使用中。如果没有,您的字体应该可以使用。

我将字体加载检测方法重写为一个 jQuery 插件,旨在让开发人员能够根据字体是否已加载来设置元素样式。添加了故障安全计时器,因此如果自定义字体无法加载,用户不会没有内容。这只是糟糕的可用性。

我还增加了对字体加载过程中发生的事情以及失败时发生的事情的更大控制,包括添加和删除类。你现在可以对字体做任何你喜欢的事情。我只建议修改字体大小、行距等,以使您的后备字体尽可能接近自定义,这样您的布局就可以保持不变,并且用户可以获得预期的体验。

这是一个演示:http://patrickmarabeas.github.io/jQuery-FontSpy.js

将以下内容放入 .js 文件并引用它。

(function($) 

    $.fontSpy = function( element, conf ) 
        var $element = $(element);
        var defaults = 
            font: $element.css("font-family"),
            onLoad: '',
            onFail: '',
            testFont: 'Comic Sans MS',
            testString: 'QW@HhsXJ',
            delay: 50,
            timeOut: 2500
        ;
        var config = $.extend( defaults, conf );
        var tester = document.createElement('span');
            tester.style.position = 'absolute';
            tester.style.top = '-9999px';
            tester.style.left = '-9999px';
            tester.style.visibility = 'hidden';
            tester.style.fontFamily = config.testFont;
            tester.style.fontSize = '250px';
            tester.innerhtml = config.testString;
        document.body.appendChild(tester);
        var fallbackFontWidth = tester.offsetWidth;
        tester.style.fontFamily = config.font + ',' + config.testFont;
        function checkFont() 
            var loadedFontWidth = tester.offsetWidth;
            if (fallbackFontWidth === loadedFontWidth)
                if(config.timeOut < 0) 
                    $element.removeClass(config.onLoad);
                    $element.addClass(config.onFail);
                    console.log('failure');
                
                else 
                    $element.addClass(config.onLoad);
                    setTimeout(checkFont, config.delay);
                    config.timeOut = config.timeOut - config.delay;
                
            
            else 
                $element.removeClass(config.onLoad);
            
        
        checkFont();
    ;

    $.fn.fontSpy = function(config) 
        return this.each(function() 
            if (undefined == $(this).data('fontSpy')) 
                var plugin = new $.fontSpy(this, config);
                $(this).data('fontSpy', plugin);
            
        );
    ;

)(jQuery);

将其应用到您的项目中

.bannerTextChecked 
        font-family: "Lobster";
        /* don't specify fallback font here, do this in onFail class */


$(document).ready(function() 

    $('.bannerTextChecked').fontSpy(
        onLoad: 'hideMe',
        onFail: 'fontFail anotherClass'
    );

);

删除那个 FOUC!

.hideMe 
    visibility: hidden !important;


.fontFail 
    visibility: visible !important;
    /* fall back font */
    /* necessary styling so fallback font doesn't break your layout */

编辑:FontAwesome 兼容性已被删除,因为它无法正常工作并遇到不同版本的问题。可以在这里找到一个 hacky 修复:https://github.com/patrickmarabeas/jQuery-FontFaceSpy.js/issues/1

【讨论】:

比较字体长度...这也是 WebFont Loader(参见另一个答案)正在做的吗? 在任何情况下,比较字符长度对于许多字体都不起作用,因为许多“复制”字体被设计为具有相等长度。例如,Arial、Helvetica 和 Liberation Sans 的所有字符都具有 相同 字符宽度。另见en.wikipedia.org/wiki/Arial。到目前为止,似乎使用画布逐像素检查可能是唯一万无一失的选择..... 我需要使用它来解决 iScroll 在加载字体之前计算元素大小错误时遇到的问题。但我没有使用 jQuery,所以制作了一个香草 js 版本:github.com/keithswright/vanilla-fontspy 似乎对我有用。 @Pacerier - 请注意,只有您选择的测试和您选择的加载字体需要不同的长度。因此,除非 Comic Sans 有很多字体与它具有相同的字符宽度,否则在大多数情况下这应该仍然有效。【参考方案2】:

试试由 Google 和 Typekit 开发的WebFont Loader (github repo)。

This example首先以默认衬线字体显示文本;然后在加载字体后,它会以指定的字体显示文本。 (此代码重现了 Firefox 在所有其他现代浏览器中的默认行为。)

【讨论】:

【参考方案3】:

这是一种与其他解决方案不同的方法。

我正在使用 FontAwesome 4.1.0 来构建 WebGL 纹理。这让我想到了使用一个小画布来渲染一个 fa-square,然后检查该画布中的一个像素以测试它是否已加载:

function waitForFontAwesome( callback ) 
   var retries = 5;

   var checkReady = function() 
      var canvas, context;
      retries -= 1;
      canvas = document.createElement('canvas');
      canvas.width = 20;
      canvas.height = 20;
      context = canvas.getContext('2d');
      context.fillStyle = 'rgba(0,0,0,1.0)';
      context.fillRect( 0, 0, 20, 20 );
      context.font = '16pt FontAwesome';
      context.textAlign = 'center';
      context.fillStyle = 'rgba(255,255,255,1.0)';
      context.fillText( '\uf0c8', 10, 18 );
      var data = context.getImageData( 2, 10, 1, 1 ).data;
      if ( data[0] !== 255 && data[1] !== 255 && data[2] !== 255 ) 
         console.log( "FontAwesome is not yet available, retrying ..." );
         if ( retries > 0 ) 
            setTimeout( checkReady, 200 );
         
       else 
         console.log( "FontAwesome is loaded" );
         if ( typeof callback === 'function' ) 
            callback();
         
      
   

   checkReady();
;

由于它使用画布,因此需要一个相当现代的浏览器,但它可能也可以在 IE8 上使用 polyfill。

【讨论】:

在 KonvaJS 中加载 font-awesome 时遇到同样的问题【参考方案4】:

其实有一个很好的方法可以理解所有字体是否开始下载或加载是否完全并陷入一些错误,但它不只是针对特定字体,注意以下代码:

document.fonts.onloading = () => 
  // do someting when fonts begin to download
;
document.fonts.onloadingdone = () => 
  // do someting when fonts are loaded completely
;
document.fonts.onloading = () => 
  // do someting when fonts fall into some error
;

还有一个返回Promise的选项,它可以处理.then函数:

document.fonts.ready
 .then(() => console.log('do someting at the final with each status'))

【讨论】:

谢谢。它完全有效!但我需要通过在某处放置一个使用该字体的 元素来触发字体加载。 IE11 不支持,遗憾的是它仍然与 Windows Server 捆绑在一起 @GuyPassy,我不知道IE11是什么!! @AmerllicA 我希望有一天我能这么幸运【参考方案5】:

这里有另一种无需使用计时器就可以知道@font-face 是否已经加载的方法:当精心设计的元素的大小发生变化时,利用“滚动”事件接收瞬时事件。

我写了一封blog post 来说明它是如何完成的,并发布了library on Github。

【讨论】:

【参考方案6】:

试试类似的东西

$(window).bind("load", function() 
       $('#text').addClass('shown');
);

然后做

#text visibility: hidden;
#text.shown visibility: visible;

加载字体后应该触发 load 事件。

【讨论】:

这与加载整个页面时运行的$(function()...) 相同。 不一样。 hayk.mart 的示例将在页面内的 DOM (HTML) 和资产(CSS、JS、图像、框架)完成加载时触发。只有 DOM 完成加载时的示例。 很好奇为什么这个答案被否决,快速搜索表明这是正确的方法,例如eager.io/blog/how-to-decide-when-your-code-should-run【参考方案7】:

或者,您可以将font-display: block 添加到您的@font-face 声明中。

这指示浏览器将备用字体呈现为不可见,直到您的字体被加载,不需要display: none 或任何 javascript 加载字体检测

【讨论】:

【参考方案8】:

Typescript、Angular 的解决方案。

如果您正在使用 Angular,则可以使用此 module 来进行字体检查。

// document.fonts.check extension
import type  from 'css-font-loading-module';

ngOnInit() 
  this.onFontLoad();


public onFontLoad() 
  let myTimer = setInterval(() => 
    if (document.fonts.check('14px MyFont')) 
      console.log('Font is loaded!');
      clearInterval(myTimer);
     else 
      console.log('Font is loading');
    
  , 1);

另外,有些字体非常重。因此,您可以在加载字体时添加加载屏幕,并在加载字体时移除加载屏幕。我相信这是一种更好的方法,而不是将您的 CSS 类更改为 display: none,仅仅是因为如果用户的互联网速度较慢,下载某些字体可能需要 3-4 秒以上。

【讨论】:

【参考方案9】:

这是一种替代方法,至少可以确保加载 font-awesome,不是 OP 的完整解决方案。在此处的 wordpress 论坛中找到原始代码 https://wordpress.stackexchange.com/a/165358/40636。

它是不可知的,可以与任何字体样式资源一起使用,例如可以检查字体系列的 font-awesome。再多思考一下,我敢打赌这可以应用于更多......

<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<script>
    (function($)
        var faSpan = $('<span class="fa" style="display:none"></span>').appendTo('body');
        if (faSpan .css('fontFamily') !== 'FontAwesome' ) 
            // Fallback Link
            $('head').append('<link href="/css/font-awesome.min.css" rel="stylesheet">');
        
        faSpan.remove();
    )(jQuery);
</script>

【讨论】:

这是在 Font-Awesome 未加载(或 if it loads too slowly!)但不通知何时字体完成加载的情况下的后备。 @DanDascalescu 我已经更新了答案,以更清楚地表明这是一种替代方法,只能确保加载 font-awesome 库,而不是完整的解决方案。希望这能澄清一点,因为我在上一次迭代中获得了一些反对意见。【参考方案10】:

使用下面的代码:

<!DOCTYPE HTML>
<html>
    <head>
    </head>

<body>
<canvas id="canvasFont"   style="position: absolute; display: none;"></canvas>

<script>
function IsLoadedFonts()
    
        var Args = arguments;
        var obj = document.getElementById('canvasFont');
        var ctx = obj.getContext("2d");
        var baseFont = (/chrome/i.test(navigator.userAgent))?'tims new roman':'arial';
         //................
          function getImg(fon)
           
            ctx.clearRect(0, 0, (obj).width, (obj).height);
            ctx.fillStyle = 'rgba(0,0,0,1.0)';
            ctx.fillRect( 0, 0, 40, 40 );
            ctx.font = '20px '+ fon;
            ctx.textBaseline = "top";
            ctx.fillStyle = 'rgba(255,255,255,1.0)';
            ctx.fillText( '\u0630', 18, 5 );
            return ctx.getImageData( 0, 0, 40, 40 );
          ;
        //..............
          for(var i1=0; i1<Args.length; i1++)
          
            data1 = getImg(Args[i1]);
            data2 = getImg(baseFont);
            var isLoaded = false;
            //...........
            for (var i=0; i<data1.data.length; i++)
            
                if(data1.data[i] != data2.data[i])
                    isLoaded = true; break;
            
            //..........
            if(!isLoaded)
                    return false;
         
         return true;
    ;

     setTimeout(function()alert(IsLoadedFonts('myfont'));,100);
   </script>
   </body>

可以检查多种字体:

setTimeout(function()alert(IsLoadedFonts('font1','font2','font3'));,100);

以下代码仅在opera中有效,但很简单:

if(!document.defaultView.getComputedStyle(document.getElementById('mydiv'))['fontFamily'].match(/myfont/i))
          alert("font do not loaded ");

【讨论】:

与Leeft posted a year earlier 相同的“渲染到画布”想法。

以上是关于如何知道字体(@font-face)是不是已经加载?的主要内容,如果未能解决你的问题,请参考以下文章

Chrome 中 @font-face 的可怕渲染

无法使用 @font-face 加载字体

使用 Webpack 和 font-face 加载字体

预加载@font-face 字体?

nuxt.js - 预加载 .woff 字体加载为@font-face

在 CSS 中加载 @font-face 时如何删除浏览器中的默认字体? “FOUT”