110

当我使用通过@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 width="250" height="250">
                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>
4

18 回答 18

80

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

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

<div style="font-family: PressStart;">.</div>
于 2010-05-03T07:14:09.463 回答
26

使用此技巧并将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://stackoverflow.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);
};
于 2011-11-22T07:50:17.833 回答
21

在画布中使用字体之前,您可以使用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 的规范在撰写本文时是一个工作草案。请参阅此处的浏览器兼容性表。

于 2016-03-27T14:09:19.183 回答
16

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

看看 Google 的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);
  })();
于 2011-09-02T22:41:13.283 回答
14

使用简单的 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>
于 2014-10-23T04:21:46.820 回答
9

https://drafts.c​​sswg.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');

});
于 2017-02-07T17:35:20.697 回答
3

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

通过直接在 CSS 中将字体系列添加到画布来解决它,所以你可以添加

画布{字体系列:按开始;}

于 2010-05-03T11:55:08.543 回答
3

这篇文章整理了我关于延迟加载字体不显示的问题。

如何加载网络字体以避免性能问题并加快页面加载

这帮助了我...

    <link rel="preload" as="font" href="assets/fonts/Maki2/fontmaki2.css" rel="stylesheet" crossorigin="anonymous">
于 2020-05-15T00:46:36.773 回答
1

我不确定这是否会对您有所帮助,但为了解决我的代码问题,我只是在我的 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);
    }
于 2013-03-08T02:06:50.827 回答
1

我在这里写了一个 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" width="740" height="400"></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 来演示。

于 2013-05-06T19:42:59.153 回答
1

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

于 2015-06-14T10:59:47.593 回答
1

The canvas is drawn independently of the DOM loading. The preload technique will only work if the canvas is drawn after the preload.

My solution, even if it is not the best:

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();
}
于 2018-03-24T11:46:05.570 回答
0

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

$('body').append("<div id='loadfont' style='font-family: myfont;'>.</div>");
$('#loadfont').remove();
于 2012-07-29T12:17:43.033 回答
0

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

于 2011-11-05T01:46:04.523 回答
0

我的回答是针对 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

于 2013-05-01T16:53:12.887 回答
0

我尝试使用 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找到浏览器兼容性

于 2018-12-05T16:26:03.900 回答
0

如果您想在每次加载新字体时重绘(并且可能会更改渲染),那么字体加载 api 也有一个很好的事件。我在完全动态的环境中遇到了 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
}
于 2017-12-01T07:19:34.100 回答
-3

添加延迟如下

<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>
于 2015-11-03T19:59:38.927 回答