2

我正在尝试在<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,但这也意味着当用户第一次下载网络字体时,他们将看到如上所示的无样式文本。

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


我目前的解决方法

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

简而言之,它会创建一个<span>元素,其中包含一些默认字体的文本,测量宽度/高度,将元素字体设置为 Web 字体,再次测量大小并比较结果。如果大小发生了变化,则假定 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);
    }
}

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

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

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

4

1 回答 1

3

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

我还不能称其为理想世界,但您实际上可以做到这一点。

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)which 将加载渲染所需的所有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>

于 2020-12-02T06:16:57.850 回答