Warm tip: This article is reproduced from serverfault.com, please click

javascript-如何检测何时通过CSS的“ unicode-range”描述符下载字体?

(javascript - How can I detect when a font is downloaded via CSS's "unicode-range" descriptor?)

发布于 2020-12-01 17:09:20

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

当前外观:

未加载Web字体的画布

下面是它的应该看:

带有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字体时,他们将看到如上所示的未样式化文本。

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


我目前的解决方法

我已经修改了一个关于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);
    }
}

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

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

我觉得必须有一种更干净,更优雅的方式来做到这一点。有人可以提供任何建议吗?

Questioner
user7290573
Viewed
11
Kaiido 2020-12-02 14:16:57

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

我还不会将其称为理想世界,但实际上你可以做到。

document.fonts.ready当在页面上呈现可见文本所需的所有字体都已加载时Promise就会解析。

不远处,你可以遍历document.fonts所有已声明FontFace,并检查它们是否已加载以及定义的unicode范围(如果需要)。

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)字体,可以调用它将加载渲染所需的所有FontFacesthe_text_to_render

( 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;
}
<canvas></canvas>