第一次使用@ font-face将文本绘制到<canvas>无效


93

当我在具有通过@ 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>

1
浏览器以异步方式在后台加载字体。这是正常的行为。另请参阅paulirish.com/2009/fighting-the-font-face-fout
Thomas

Answers:


71

在画布上绘制必须发生,并在调用该fillText方法时立即返回。但是,浏览器尚未从网络加载字体,这是一项后台任务。因此,它必须退回到它确实可用的字体。

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

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

3
您可能会想添加display: none,但这可能会导致浏览器跳过字体的加载。最好使用空格而不是.
汤玛斯(Thomas)

6
使用空格将导致IE丢弃应该在div中的空白节点,而不会在字体中保留任何文本。IE当然还不支持画布,因此,尚不清楚future-IE是否会继续这样做,并且这是否会对字体加载行为产生影响,但这是一个长期存在的IE HTML解析问题。
2010年

1
有没有更简单的方法来预加载字体?例如以某种方式强制通过javascript?
2012年

@Joshua:仅通过在页面上创建元素并在其上设置字体即可。创建与上述相同但又动态的内容。
bobince 2012年

17
添加此选项并不能保证在执行JavaScript时已经加载了字体。我必须使用有问题的延迟字体(setTimeout)执行脚本,这当然是不好的。
尼克

23

使用此技巧并将onerror事件绑定到Image元素。

此处演示:在最新的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 /programming/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);
};

3
聪明的把戏。请注意,尽管您正在加载css,该css包含对真实字体文件的引用(例如.ttf,.woff等)。我必须两次使用技巧,一次用于css文件,一次用于参考字体文件(.woff),以确保所有内容均已加载。
岩浆

1
使用.ttf字体尝试了这种方法-在Chrome(41.0.2272.101 m)上无法稳定运行。即使5秒钟内的setTimeout也无济于事-第一个渲染使用默认字体。
Mikhail Fiadosenka

2
您应该先设置onerror处理程序,然后再设置src
asdjfiasd

1
也是“新形象”;缺少括号。
asdjfiasd

@asdjfiasd不需要parens,即使src设置了属性,也将在下一个执行块中开始加载。
2015年

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);
  })();

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的规范是一个工作草案。请参阅此处的浏览器兼容性表。


1
你不见了document.fonts.add。请参阅bruno的答案。
Pacerier's

谢谢@Pacerier!更新了我的答案。
Fred Bergman

此技术是实验性的,大多数浏览器均不支持。
Michael Zelensky

11

如何使用简单的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>



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);
    }

我在上面添加了一些代码来说明我的答案。开发另一个网页时,我遇到了类似的问题,这解决了该问题,因为在服务器端加载了所有字体,从而允许它们正确显示在网页上。
grapien

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进行演示。


1

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


0

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


0

我的回答是针对Google Web字体而不是@ font-face。我到处搜索字体不出现在画布上的解决方案。我尝试了计时器,setInterval,字体延迟库和各种技巧。没事。(包括将font-family放入画布的CSS或canvas元素的ID。)

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

这也不起作用-直到我还添加了一个短(100ms)计时器延迟。到目前为止,我仅在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


0

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

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

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
}

0

画布是独立于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();
}



-4

如下添加延迟

<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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.