【问题标题】:How can I detect when a font is downloaded via CSS's "unicode-range" descriptor?如何检测何时通过 CSS 的“unicode-range”描述符下载字体?
【发布时间】:2021-03-13 15:35:09
【问题描述】:

我正在尝试在 <canvas> 元素上显示自定义 Web 字体,但由于尚未预加载字体,它并不总是第一次正确显示:

目前的样子:

这是它应该的样子:


我知道存在几种预加载网络字体的方法,但遗憾的是它们不适用于这种情况,因为页面可以使用多达 90 种字体。

数量大的原因是因为我使用的中文字体已被分割成许多单独的.woff2 文件,因此我的 CSS 文件可以像这样使用unicode-range 描述符:

/* unicode range [1] */
@font-face {
  font-family: 'ma-shan-zheng';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local('Ma Shan Zheng Regular'), local('MaShanZheng-Regular'), url(ma-shan-zheng.5.woff2) format('woff2');
  unicode-range: U+fee3, U+fef3, U+ff03-ff04, U+ff07, U+ff0a, U+ff17-ff19, U+ff1c-ff1d, U+ff20-ff3a, U+ff3c, U+ff3e-ff5b, U+ff5d, U+ff61-ff65, U+ff67-ff6a, U+ff6c, U+ff6f-ff78, U+ff7a-ff7d, U+ff80-ff84, U+ff86, U+ff89-ff8e, U+ff92, U+ff97-ff9b, U+ff9d-ff9f, U+ffe0-ffe4, U+ffe6, U+ffe9, U+ffeb, U+ffed, U+fffc, U+1f004, U+1f170-1f171, U+1f192-1f195, U+1f198-1f19a, U+1f1e6-1f1e8;
}

/* unicode range [2] */
@font-face {
  font-family: 'ma-shan-zheng';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local('Ma Shan Zheng Regular'), local('MaShanZheng-Regular'), url(ma-shan-zheng.6.woff2) format('woff2');
  unicode-range: U+f0a7, U+f0b2, U+f0b7, U+f0c9, U+f0d8, U+f0da, U+f0dc-f0dd, U+f0e0, U+f0e6, U+f0eb, U+f0fc, U+f101, U+f104-f105, U+f107, U+f10b, U+f11b, U+f14b, U+f18a, U+f193, U+f1d6-f1d7, U+f244, U+f27a, U+f296, U+f2ae, U+f471, U+f4b3, U+f610-f611, U+f880-f881, U+f8ec, U+f8f5, U+f8ff, U+f901, U+f90a, U+f92c-f92d, U+f934, U+f937, U+f941, U+f965, U+f967, U+f969, U+f96b, U+f96f, U+f974, U+f978-f979, U+f97e, U+f981, U+f98a, U+f98e, U+f997, U+f99c, U+f9b2, U+f9b5, U+f9ba, U+f9be, U+f9ca, U+f9d0-f9d1, U+f9dd, U+f9e0-f9e1, U+f9e4, U+f9f7, U+fa00-fa01, U+fa08, U+fa0a, U+fa11, U+fb01-fb02, U+fdfc, U+fe0e, U+fe30-fe31, U+fe33-fe44, U+fe49-fe52, U+fe54-fe57, U+fe59-fe66, U+fe68-fe6b, U+fe8e, U+fe92-fe93, U+feae, U+feb8, U+fecb-fecc, U+fee0;
}

/* etc. all the way up to [90] */

这样做的明显好处是只在需要时才下载相关的.woff2 文件,但这也意味着当用户第一次下载网络字体时,他们将看到如上所示的无样式文本。

在理想情况下,我可以将回调函数附加到字体的自动下载中,但似乎无法访问浏览器的这部分行为。


我目前的解决方法

我已经从一个关于 web 字体预加载的旧 SO 问题修改了一个解决方案 - 它非常 hacky,但它确实有效。

简而言之,它会创建一个<span> 元素,其中包含一些默认字体的文本,测量宽度/高度,将元素字体设置为网络字体,再次测量大小并比较结果。如果大小发生了变化,则假定 Web 字体已加载:

function waitFontLoaded(font, phrase, callback) {
    var node = document.createElement("span");

    // Set node content to the desired phrase/text
    node.innerHTML = phrase;

    // 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 size with no applied web font
    var width  = node.offsetWidth;
    var height = node.offsetHeight;

    node.style.fontFamily = font + ", sans-serif";

    var interval;

    // Compare current size with original size
    function checkFont() {
        if (node && (node.offsetWidth !== width || node.offsetHeight !== height)) {
            node.parentNode.removeChild(node);
            node = null;

            clearInterval(interval);

            callback();

            return true;
        }

        return false;
    }

    if (!checkFont()) {
        interval = setInterval(checkFont, 50);
    }
}

正如我所说,这确实有效,但显然不是一个可靠的解决方案,因为两个字符在默认系统字体和网络字体中的大小相同并非不可能。

另一个非常 hacky 的解决方案是每秒刷新一次 <canvas> 元素,例如通过使用setInterval

我觉得必须有一种更清洁、更优雅的方式来做到这一点。谁能给点建议?

【问题讨论】:

    标签: javascript canvas fonts cjk unicode-range


    【解决方案1】:

    在理想情况下,我可以将回调函数附加到字体的自动下载中

    我还不认为这是一个理想的世界,但实际上你可以做到。

    document.fonts.ready Promise 在页面上呈现可见文本所需的所有字体都已加载后解决。

    离那里不远,您可以遍历document.fonts,它包含所有已声明的FontFaces,并检查它们是否已加载,以及它们定义的unicode-range(如果需要)。

    document.fonts.ready.then( () => {
      const loaded_fonts = [ ...document.fonts ]
        // simplify the objects for logging here
        .map( ({unicodeRange, status}, index) => ({ unicodeRange, status, index }) )
        .filter( ({status}) => status === "loaded" );
    
      console.log( loaded_fonts );
    });
    /* cyrillic-ext index:0 */
    @font-face {
      font-family: 'Roboto';
      font-style: normal;
      font-weight: 400;
      src: url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.woff2) format('woff2');
      unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
    }
    /* cyrillic index:1*/
    @font-face {
      font-family: 'Roboto';
      font-style: normal;
      font-weight: 400;
      src: url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu5mxKKTU1Kvnz.woff2) format('woff2');
      unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
    }
    /* greek-ext index:2*/
    @font-face {
      font-family: 'Roboto';
      font-style: normal;
      font-weight: 400;
      src: url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu7mxKKTU1Kvnz.woff2) format('woff2');
      unicode-range: U+1F00-1FFF;
    }
    /* greek index:3*/
    @font-face {
      font-family: 'Roboto';
      font-style: normal;
      font-weight: 400;
      src: url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu4WxKKTU1Kvnz.woff2) format('woff2');
      unicode-range: U+0370-03FF;
    }
    /* vietnamese index:4 - should be loaded */
    @font-face {
      font-family: 'Roboto';
      font-style: normal;
      font-weight: 400;
      src: url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu7WxKKTU1Kvnz.woff2) format('woff2');
      unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
    }
    /* latin-ext index:5 - should be loaded */
    @font-face {
      font-family: 'Roboto';
      font-style: normal;
      font-weight: 400;
      src: url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu7GxKKTU1Kvnz.woff2) format('woff2');
      unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
    }
    /* latin index:6 - should be loaded */
    @font-face {
      font-family: 'Roboto';
      font-style: normal;
      font-weight: 400;
      src: url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2) format('woff2');
      unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
    }
    
    
    body {
      font-family: "Roboto";
    }
    Hello thế giới

    如果您需要在画布上使用特定字体之前加载它,您可以调用document.fonts.load("your font", the_text_to_render),它将加载渲染the_text_to_render 所需的所有FontFaces

    ( async () => {
      // <DEMO only>
      // just to be sure the font was not loaded yet
      await document.fonts.ready;
      logLoadedFontsCount( "after document.fonts ready" );
      // </DEMO only>
      
      // now try to draw using that font face anyway
      const canvas = document.querySelector( "canvas" );
      const ctx = canvas.getContext( "2d" );
      const font_shorthand = "30px Roboto";
      const text = "Привет мир";
    
      // force loading fonts
      await document.fonts.load( font_shorthand, text );
      // now we can use it
      ctx.font = font_shorthand;
      ctx.fillText( text, 30, 50 );
    
      // <DEMO only>
      logLoadedFontsCount( "after loading of customs fonts" );
      // </DEMO only>
    } )();
    
    // <DEMO only>
    // logs how many FontFaces are currently loaded
    function logLoadedFontsCount( when = "" ) {
      const loaded_fonts = [ ...document.fonts ]
        .filter( ({status}) => status === "loaded" );
      console.log( "%s fonts loaded %s", loaded_fonts.length, when );
    } 
    // </DEMO only>
    /* cyrillic-ext index:0 */
    @font-face {
      font-family: 'Roboto';
      font-style: normal;
      font-weight: 400;
      src: url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.woff2) format('woff2');
      unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
    }
    /* cyrillic index:1*/
    @font-face {
      font-family: 'Roboto';
      font-style: normal;
      font-weight: 400;
      src: url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu5mxKKTU1Kvnz.woff2) format('woff2');
      unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
    }
    /* greek-ext index:2*/
    @font-face {
      font-family: 'Roboto';
      font-style: normal;
      font-weight: 400;
      src: url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu7mxKKTU1Kvnz.woff2) format('woff2');
      unicode-range: U+1F00-1FFF;
    }
    /* greek index:3*/
    @font-face {
      font-family: 'Roboto';
      font-style: normal;
      font-weight: 400;
      src: url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu4WxKKTU1Kvnz.woff2) format('woff2');
      unicode-range: U+0370-03FF;
    }
    /* vietnamese index:4 - should be loaded */
    @font-face {
      font-family: 'Roboto';
      font-style: normal;
      font-weight: 400;
      src: url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu7WxKKTU1Kvnz.woff2) format('woff2');
      unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
    }
    /* latin-ext index:5 - should be loaded */
    @font-face {
      font-family: 'Roboto';
      font-style: normal;
      font-weight: 400;
      src: url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu7GxKKTU1Kvnz.woff2) format('woff2');
      unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
    }
    /* latin index:6 - should be loaded */
    @font-face {
      font-family: 'Roboto';
      font-style: normal;
      font-weight: 400;
      src: url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2) format('woff2');
      unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
    }
    &lt;canvas&gt;&lt;/canvas&gt;

    【讨论】:

    • 非常有帮助的答案,谢谢;不知道document.fonts
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-25
    • 1970-01-01
    • 2021-01-07
    • 1970-01-01
    • 1970-01-01
    • 2015-02-06
    • 2014-07-08
    相关资源
    最近更新 更多