使用@font-face 将文本绘制到 <canvas> 第一次不起作用

Posted

技术标签:

【中文标题】使用@font-face 将文本绘制到 <canvas> 第一次不起作用【英文标题】:Drawing text to <canvas> with @font-face does not work at the first time 【发布时间】:2011-02-14 22:35:40 【问题描述】:

当我使用通过@font-face 加载的字体在画布中绘制文本时,文本无法正确显示。它根本不显示(在 Chrome 13 和 Firefox 5 中),或者字体错误(Opera 11)。这种类型的意外行为仅在使用该字体的第一张绘图中发生。之后一切正常。

这是标准行为还是什么?

谢谢。

PS:以下是测试用例的源码

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>@font-face and &lt;canvas&gt;</title>
        <style id="css">
@font-face 
    font-family: 'Press Start 2P';
    src: url('fonts/PressStart2P.ttf');

        </style>
        <style>
canvas, pre 
    border: 1px solid black;
    padding: 0 1em;

        </style>
    </head>
    <body>
        <h1>@font-face and &lt;canvas&gt;</h1>
        <p>
            Description: click the button several times, and you will see the problem.
            The first line won't show at all, or with a wrong typeface even if it does.
            <strong>If you have visited this page before, you may have to refresh (or reload) it.</strong>
        </p>
        <p>
            <button id="draw">#draw</button>
        </p>
        <p>
            <canvas  >
                Your browser does not support the CANVAS element.
                Try the latest Firefox, Google Chrome, Safari or Opera.
            </canvas>
        </p>
        <h2>@font-face</h2>
        <pre id="view-css"></pre>
        <h2>Script</h2>
        <pre id="view-script"></pre>
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
        <script id="script">
var x = 30,
    y = 10;

$('#draw').click(function () 
    var canvas = $('canvas')[0],
        ctx = canvas.getContext('2d');
    ctx.font = '12px "Press Start 2P"';
    ctx.fillStyle = '#000';
    ctx.fillText('Hello, world!', x, y += 20);
    ctx.fillRect(x - 20, y - 10, 10, 10);
);
        </script>
        <script>
$('#view-css').text($('#css').text());
$('#view-script').text($('#script').text());
        </script>
    </body>
</html>

【问题讨论】:

浏览器在后台异步加载字体。这是正常行为。另见paulirish.com/2009/fighting-the-font-face-fout 【参考方案1】:

当您调用fillText 方法时,必须在画布上进行绘图并立即返回。但是,浏览器还没有从网络加载字体,这是一个后台任务。所以它必须回退到它确实可用的字体。

如果您想确保字体可用,请在页面上预加载一些其他元素,例如:

<div style="font-family: PressStart;">.</div>

【讨论】:

您可能想添加display: none,但这可能会导致浏览器跳过加载字体。最好使用空格而不是. 使用空格会导致 IE 丢弃应该在 div 中的空白节点,不留下任何文本以字体呈现。当然 IE 还不支持画布,所以未来 IE 是否会继续这样做,以及这是否会对字体加载行为产生影响尚不清楚,但这是一个长期存在的 IE HTML 解析问题。 没有更简单的方法来预加载字体吗?例如以某种方式通过javascript强制它? @Joshua:仅通过在页面上创建一个元素并在其上设置字体,即。动态创建与上面相同的内容。 添加此项并不能保证在执行 JavaScript 时已经加载了字体。我不得不使用延迟的字体(setTimeout)来执行我的脚本,这当然很糟糕。【参考方案2】:

我最近在玩它时遇到了这个问题http://people.opera.com/patrickl/experiments/canvas/scroller/

通过直接在 CSS 中将字体系列添加到画布来解决此问题,因此您只需添加

canvas font-family: PressStart;

【讨论】:

似乎不起作用:Tested in Chrome 12 编辑:刷新几次让它错过字体【参考方案3】:

问题的关键在于您正在尝试使用该字体,但浏览器尚未加载它,甚至可能还没有请求它。您需要的是能够加载字体并在加载后给您回调的东西;一旦你得到回调,你就知道可以使用字体了。

看看谷歌的WebFont Loader;它似乎是一个“自定义”提供程序和加载后的active 回调将使其工作。

我以前从未使用过它,但是通过快速扫描文档,您需要制作一个 css 文件 fonts/pressstart2p.css,如下所示:

@font-face 
  font-family: 'Press Start 2P';
  font-style: normal;
  font-weight: normal;
  src: local('Press Start 2P'), url('http://lemon-factory.net/reproduce/fonts/Press Start 2P.ttf') format('ttf');

然后添加以下JS:

  WebFontConfig = 
    custom:  families: ['Press Start 2P'],
              urls: [ 'http://lemon-factory.net/reproduce/fonts/pressstart2p.css'],
    active: function() 
      /* code to execute once all font families are loaded */
      console.log(" I sure hope my font is loaded now. ");
    
  ;
  (function() 
    var wf = document.createElement('script');
    wf.src = ('https:' == document.location.protocol ? 'https' : 'http') +
        '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
    wf.type = 'text/javascript';
    wf.async = 'true';
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(wf, s);
  )();

【讨论】:

【参考方案4】:

首先按照其他答案中的建议使用 Google 的 Web 字体加载器,并将您的绘图代码添加到它提供的回调中,以指示字体已加载。然而,这并不是故事的结局。从这一点来看,它非常依赖于浏览器。大多数情况下它会正常工作,但有时可能需要等待数百毫秒或在页面上的其他位置使用字体。我尝试了不同的选项,afaik 始终有效的一种方法是使用您要使用的字体系列和字体大小组合在画布中快速绘制一些测试消息。您可以使用与背景相同的颜色来执行此操作,因此它们甚至都不可见,并且会非常快地发生。在那之后,字体总是对我和所有浏览器都有效。

【讨论】:

【参考方案5】:

使用this trick 并将onerror 事件绑定到Image 元素。

Demo here:适用于最新的 Chrome。

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = 'http://fonts.googleapis.com/css?family=Vast+Shadow';
document.getElementsByTagName('head')[0].appendChild(link);

// Trick from https://***.com/questions/2635814/
var image = new Image();
image.src = link.href;
image.onerror = function() 
    ctx.font = '50px "Vast Shadow"';
    ctx.textBaseline = 'top';
    ctx.fillText('Hello!', 20, 10);
;

【讨论】:

聪明的把戏。请注意,您正在加载 css,而 css 又包含对真实字体文件的引用(例如,.ttf、.woff 等)。我不得不使用你的技巧两次,一次用于 css 文件,一次用于引用的字体文件 (.woff) 以确保所有内容都已加载。 用 .ttf 字体尝试了这种方法 - 在 Chrome (41.0.2272.101 m) 上无法稳定工作。即使是 5 秒内的 setTimeout 也无济于事 - 第一次渲染使用默认字体。 你应该设置 onerror handler before 你设置 src 还有“新图片;”缺少括号。 你能用本地字体做这样的事情吗?【参考方案6】:

面临同样的问题。在阅读了“bobince”和其他 cmets 之后,我使用以下 javascript 来解决它:

$('body').append("<div id='loadfont' style='font-family: myfont;'>.</div>");
$('#loadfont').remove();

【讨论】:

【参考方案7】:

我不确定这是否会对您有所帮助,但为了解决我的代码问题,我只是在我的 Javascript 顶部创建了一个 for 循环,该循环遍历了我想要加载的所有字体。然后我运行一个函数来清除画布并在画布上预加载我想要的项目。到目前为止,它运行良好。这是我的逻辑,我在下面发布了我的代码:

var fontLibrary = ["Acme","Aladin","Amarante","Belgrano","CantoraOne","Capriola","CevicheOne","Chango","ChelaOne","CherryCreamSoda",
"ConcertOne","Condiment","Damion","Devonshire","FugazOne","GermaniaOne","GorditasBold","GorditasRegular",
"KaushanScript","LeckerliOne","Lemon","LilitaOne","LuckiestGuy","Molle","MrDafoe","MrsSheppards",
"Norican","OriginalSurfer","OswaldBold","OswaldLight","OswaldRegular","Pacifico","Paprika","Playball",
"Quando","Ranchers","SansitaOne","SpicyRice","TitanOne","Yellowtail","Yesteryear"];

    for (var i=0; i < fontLibrary.length; i++) 
        context.fillText("Sample",250,50);
        context.font="34px " + fontLibrary[i];
    

    changefontType();

    function changefontType() 
        selfonttype = $("#selfontype").val();
        inputtextgo1();
    

    function inputtextgo1() 
        var y = 50;
        var lineHeight = 36;
        area1text = document.getElementById("bag1areatext").value;
        context.clearRect(0, 0, 500, 95)
        context.drawImage(section1backgroundimage, 0, 0);
        context.font="34px " + selfonttype;
        context.fillStyle = seltextcolor;
        context.fillText(area1text, 250, y);
    

【讨论】:

我在上面添加了一些代码来说明我的答案。我在开发另一个网页时遇到了类似的问题,这解决了它,因为在服务器端它加载了所有字体,因此它们可以在网页上正确显示。【参考方案8】:

我的答案是针对 Google Web 字体而不是 @font-face。我到处搜索解决字体未显示在画布中的问题。我尝试了计时器、setInterval、字体延迟库和各种技巧。没有任何效果。 (包括将 font-family 放入画布的 CSS 或画布元素的 ID。)

但是,我发现以 Google 字体呈现的动画文本很容易工作。有什么不同?在画布动画中,我们一次又一次地重新绘制动画项目。所以我有了将文本渲染两次的想法。

这也不起作用——直到我还添加了一个短的(100 毫秒)定时器延迟。到目前为止,我只在 Mac 上测试过。 Chrome 在 100 毫秒时运行良好。 Safari 需要重新加载页面,所以我将计时器增加到 1000,然后就可以了。如果我使用 Google 字体(包括动画版本),Firefox 18.0.2 和 20.0 不会在画布上加载任何内容。

完整代码:http://www.macloo.com/examples/canvas/canvas10.html

http://www.macloo.com/examples/canvas/scripts/canvas10.js

【讨论】:

【参考方案9】:

我在这里写了一个 jsfiddle,其中包含了大多数建议的修复,但没有一个解决问题。但是,我是一名新手程序员,所以可能没有正确编码建议的修复:

http://jsfiddle.net/HatHead/GcxQ9/23/

HTML:

<!-- you need to empty your browser cache and do a hard reload EVERYTIME to test this otherwise it will appear to working when, in fact, it isn't -->

<h1>Title Font</h1>

<p>Paragraph font...</p>
<canvas id="myCanvas"  ></canvas>

CSS:

@import url(http://fonts.googleapis.com/css?family=Architects+Daughter);
 @import url(http://fonts.googleapis.com/css?family=Rock+Salt);
 canvas 
    font-family:'Rock Salt', 'Architects Daughter'

.wf-loading p 
    font-family: serif

.wf-inactive p 
    font-family: serif

.wf-active p 
    font-family:'Architects Daughter', serif;
    font-size: 24px;
    font-weight: bold;

.wf-loading h1 
    font-family: serif;
    font-weight: 400;
    font-size: 42px

.wf-inactive h1 
    font-family: serif;
    font-weight: 400;
    font-size: 42px

.wf-active h1 
    font-family:'Rock Salt', serif;
    font-weight: 400;
    font-size: 42px;

JS:

// do the Google Font Loader stuff....
WebFontConfig = 
    google: 
        families: ['Architects Daughter', 'Rock Salt']
    
;
(function () 
    var wf = document.createElement('script');
    wf.src = ('https:' == document.location.protocol ? 'https' : 'http') +
        '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
    wf.type = 'text/javascript';
    wf.async = 'true';
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(wf, s);
)();

//play with the milliseconds delay to find the threshold - don't forget to empty your browser cache and do a hard reload!
setTimeout(WriteCanvasText, 0);

function WriteCanvasText() 
    // write some text to the canvas
    var canvas = document.getElementById("myCanvas");
    var context = canvas.getContext("2d");
    context.font = "normal" + " " + "normal" + " " + "bold" + " " + "42px" + " " + "Rock Salt";
    context.fillStyle = "#d50";
    context.fillText("Canvas Title", 5, 100);
    context.font = "normal" + " " + "normal" + " " + "bold" + " " + "24px" + " " + "Architects Daughter";
    context.fillText("Here is some text on the canvas...", 5, 180);

解决方法 我最终放弃了,并在第一次加载时使用了文本图像,同时还将文本与字体显示区域之外的字体一起定位。画布显示区域内字体的所有后续显示都没有问题。无论如何,这都不是一个优雅的解决方法。

该解决方案已纳入我的网站,但如果有人需要,我会尝试创建一个 jsfiddle 来演示。

【讨论】:

【参考方案10】:

使用简单的 CSS 来隐藏 div 使用这样的字体怎么样:

CSS:

#preloadfont 
  font-family: YourFont;
  opacity:0;
  height:0;
  width:0;
  display:inline-block;

HTML:

<body>
   <div id="preloadfont">.</div>
   <canvas id="yourcanvas"></canvas>
   ...
</body>

【讨论】:

【参考方案11】:

一些browsers support CSS Font Loading 规范。它允许您在加载所有字体时注册回调。您可以延迟绘制画布(或至少在画布中绘制文本),然后在字体可用时触发重绘。

【讨论】:

【参考方案12】:

如下添加延迟

<script>
var c = document.getElementById('myCanvas');    
var ctx = c.getContext('2d');
setTimeout(function() 
    ctx.font = "24px 'Proxy6'"; // uninstalled @fontface font style 
    ctx.textBaseline = 'top';
    ctx.fillText('What!', 20, 10);      
, 100); 
</script>

【讨论】:

【参考方案13】:

您可以使用FontFace API 加载字体,然后在画布中使用它:

const myFont = new FontFace('My Font', 'url(https://myfont.woff2)');

myFont.load().then((font) => 
  document.fonts.add(font);

  console.log('Font loaded');
);

首先下载字体资源myfont.woff2。下载完成后,字体会添加到文档的FontFaceSet。

在撰写本文时,FontFace API 的规范是一个工作草案。 See browser compatibility table here.

【讨论】:

你错过了document.fonts.add。请参阅布鲁诺的回答。 谢谢@Pacerier!更新了我的答案。 这项技术是实验性的,大多数浏览器都不支持。【参考方案14】:

https://drafts.csswg.org/css-font-loading/

var myFont = new FontFace('My Font', 'url(https://myfont.woff2)');

myFont.load().then(function(font)

  // with canvas, if this is ommited won't work
  document.fonts.add(font);

  console.log('Font loaded');

);

【讨论】:

很好的答案 - 谢谢【参考方案15】:

如果您想在每次加载新字体时重绘(并且可能会更改渲染),那么字体加载 API 也有一个不错的 event。我在完全动态的环境中遇到了 Promise 的问题。

var fontFaceSet = document.fonts;
if (fontFaceSet && fontFaceSet.addEventListener) 
    fontFaceSet.addEventListener('loadingdone', function () 
        // Redraw something

    );
 else 
    // no fallback is possible without this API as a font files download can be triggered
    // at any time when a new glyph is rendered on screen

【讨论】:

【参考方案16】:

画布的绘制独立于 DOM 加载。只有在预加载后绘制画布时,预加载技术才有效。

我的解决方案,即使不是最好的:

CSS:

.preloadFont 
    font-family: 'Audiowide', Impact, Charcoal, sans-serif, cursive;
    font-size: 0;
    position: absolute;
    visibility: hidden;

HTML:

<body onload="init()">
  <div class="preloadFont">.</div>
  <canvas id="yourCanvas"></canvas>
</body>

JavaScript:

function init() 
    myCanvas.draw();

【讨论】:

【参考方案17】:

我尝试使用 FontFaceSet.load 来解决问题: https://jsfiddle.net/wengshenshun/gr1zkvtq/30

const prepareFontLoad = (fontList) => Promise.all(fontList.map(font => document.fonts.load(font)))

您可以从https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/load找到浏览器兼容性

【讨论】:

【参考方案18】:

这篇文章解决了我懒加载字体不显示的问题。

How to load web fonts to avoid performance issues and speed up page loading

这对我有帮助...

    <link rel="preload" as="font" href="assets/fonts/Maki2/fontmaki2.css" rel="stylesheet" crossorigin="anonymous">

【讨论】:

以上是关于使用@font-face 将文本绘制到 <canvas> 第一次不起作用的主要内容,如果未能解决你的问题,请参考以下文章

如何将 <link rel=preload> 应用于@font-face?

向文本绘制文本时出现字体错误

如何在 C++ 中使用 glutBitmapString() 将文本绘制到屏幕上?

使用 @font-face 时 SVG node.getBBox 不正确

css @font-face 不会在所有浏览器中垂直对齐文本

无法将文本绘制到自定义视图