Das Zeichnen von Text nach <canvas> mit @ font-face funktioniert beim ersten Mal nicht


92

Wenn ich einen Text in eine Leinwand mit einer Schriftart zeichne, die über @ font-face geladen wird, wird der Text nicht richtig angezeigt. Es wird überhaupt nicht angezeigt (in Chrome 13 und Firefox 5) oder die Schrift ist falsch (Opera 11). Diese Art von unerwartetem Verhalten tritt nur bei der ersten Zeichnung mit der Schrift auf. Danach funktioniert alles gut.

Ist es das Standardverhalten oder so?

Danke dir.

PS: Es folgt der Quellcode des Testfalls

<!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
Browser laden die Schriftart asynchron im Hintergrund. Dies ist normales Verhalten. Siehe auch paulirish.com/2009/fighting-the-font-face-fout
Thomas

Antworten:


71

Das Zeichnen auf Leinwand muss erfolgen und sofort zurückkehren, wenn Sie die fillTextMethode aufrufen . Der Browser hat die Schriftart jedoch noch nicht aus dem Netzwerk geladen, was eine Hintergrundaufgabe ist. Es muss also auf die verfügbare Schriftart zurückgreifen .

Wenn Sie sicherstellen möchten, dass die Schriftart verfügbar ist, laden Sie ein anderes Element auf der Seite vor, z. B.:

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

3
Sie könnten versucht sein, etwas hinzuzufügen display: none, aber dies könnte dazu führen, dass Browser das Laden der Schriftart überspringen. Es ist besser, ein Leerzeichen anstelle eines zu verwenden ..
Thomas

6
Wenn Sie ein Leerzeichen verwenden, wirft der IE den Leerzeichen-Knoten weg, der sich im div befinden soll, und lässt keinen Text zum Rendern in der Schriftart übrig. Natürlich unterstützt der IE Canvas noch nicht, daher ist nicht bekannt, ob der zukünftige IE dies weiterhin tun wird und ob sich dies auf das Ladeverhalten von Schriftarten auswirken würde, aber es handelt sich um ein langjähriges Problem beim Parsen von IE-HTML.
Bobince

1
Gibt es keine einfachere Möglichkeit, die Schriftart vorzuladen? zB irgendwie durch Javascript zwingen?
Joshua

@Joshua: nur durch Erstellen eines Elements auf der Seite und Festlegen der Schriftart darauf, dh. Erstellen des gleichen Inhalts wie oben, jedoch dynamisch.
Bobince

17
Das Hinzufügen garantiert nicht, dass die Schriftart bereits geladen wird, wenn JavaScript ausgeführt wird. Ich musste mein Skript mit der fraglichen Schriftart verzögert (setTimeout) ausführen, was natürlich schlecht ist.
Nick

23

Verwenden Sie diesen Trick und binden Sie ein onerrorEreignis an ein ImageElement.

Demo hier : funktioniert auf dem neuesten 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
kluger Trick. Beachten Sie jedoch, dass Sie das CSS laden, das wiederum einen Verweis auf die reale Schriftartdatei enthält (z. B. .ttf, .woff usw.). Ich musste Ihren Trick zweimal anwenden, einmal für die CSS-Datei und einmal für die referenzierte Schriftartdatei (.woff), um sicherzustellen, dass alles geladen war.
Magma

1
Versuchte diesen Ansatz mit der Schriftart .ttf - funktioniert unter Chrome (41.0.2272.101 m) nicht stabil. Selbst das setTimeout in 5 Sekunden hilft nicht - das erste Rendern erfolgt mit der Standardschriftart.
Mikhail Fiadosenka

2
Sie sollten einen
Fehlerhandler

1
auch "neues Bild"; hat fehlende Klammern.
Asdjfiasd

@asdjfiasd Die Parens sind nicht erforderlich, und obwohl das srcAttribut festgelegt ist, beginnt das Laden im nächsten Ausführungsblock.
Ein bezahlter Nerd

16

Der Kern des Problems besteht darin, dass Sie versuchen, die Schriftart zu verwenden, der Browser sie jedoch noch nicht geladen und möglicherweise nicht einmal angefordert hat. Was Sie brauchen, ist etwas, das die Schriftart lädt und Ihnen einen Rückruf gibt, sobald sie geladen ist; Sobald Sie den Rückruf erhalten, wissen Sie, dass es in Ordnung ist, die Schriftart zu verwenden.

Schauen Sie sich den WebFont Loader von Google an . Es scheint, als würde ein "benutzerdefinierter" Anbieter und ein activeRückruf nach dem Laden funktionieren.

Ich habe es noch nie benutzt, aber nach einem kurzen Scan der Dokumente müssen Sie eine CSS-Datei erstellen fonts/pressstart2p.css, wie folgt :

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

Fügen Sie dann das folgende JS hinzu:

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

12

Sie können Schriftarten mit der FontFace-API laden, bevor Sie sie im Canvas verwenden:

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

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

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

Die Schriftartressource myfont.woff2wird zuerst heruntergeladen. Sobald der Download abgeschlossen ist, wird die Schriftart dem FontFaceSet des Dokuments hinzugefügt .

Die Spezifikation der FontFace-API ist zum Zeitpunkt dieses Schreibens ein Arbeitsentwurf. Siehe Browserkompatibilitätstabelle hier.


1
Du vermisst document.fonts.add. Siehe Brunos Antwort.
Pacerier

Danke @Pacerier! Meine Antwort wurde aktualisiert.
Fred Bergman

Diese Technologie ist experimentell und wird von den meisten Browsern nicht unterstützt.
Michael Zelensky

11

Was ist mit der Verwendung von einfachem CSS, um ein Div mit der folgenden Schriftart auszublenden:

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

Ich bin nicht sicher, ob dies Ihnen helfen wird, aber um das Problem mit meinem Code zu lösen, habe ich einfach eine for-Schleife oben in meinem Javascript erstellt, die alle Schriftarten durchlief, die ich laden wollte. Ich habe dann eine Funktion ausgeführt, um die Leinwand zu löschen und die gewünschten Elemente auf der Leinwand vorzuladen. Bisher hat es perfekt funktioniert. Das war meine Logik. Ich habe meinen Code unten gepostet:

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

Ich habe oben einen Code hinzugefügt, um meine Antwort zu veranschaulichen. Ich hatte ein ähnliches Problem bei der Entwicklung einer anderen Webseite und dies löste es, da auf dem Server alle Schriftarten geladen wurden, sodass sie korrekt auf der Webseite angezeigt werden können.
Grapien

1

Ich habe eine jsfiddle geschrieben, die die meisten der hier vorgeschlagenen Korrekturen enthält, aber keine hat das Problem behoben. Ich bin jedoch ein unerfahrener Programmierer und habe die vorgeschlagenen Korrekturen möglicherweise nicht richtig codiert:

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

Problemumgehung Ich gab schließlich nach und verwendete beim ersten Laden ein Bild des Textes, während ich den Text mit den Schriftflächen außerhalb des Leinwandanzeigebereichs positionierte. Alle nachfolgenden Anzeigen der Schriftarten im Canvas-Anzeigebereich funktionierten problemlos. Dies ist keineswegs eine elegante Problemumgehung.

Die Lösung ist in meine Website integriert, aber wenn jemand sie benötigt, werde ich versuchen, eine jsfiddle zu erstellen, um dies zu demonstrieren.


1

Einige Browser unterstützen die CSS- Spezifikation zum Laden von Schriftarten . Sie können sich registrieren und einen Rückruf registrieren, wenn alle Schriftarten geladen wurden. Sie können das Zeichnen Ihrer Leinwand (oder zumindest das Zeichnen von Text in Ihre Leinwand) bis dahin verzögern und eine Neuzeichnung auslösen, sobald die Schriftart verfügbar ist.


0

Verwenden Sie zunächst den Web Font Loader von Google, wie in der anderen Antwort empfohlen, und fügen Sie Ihren Zeichnungscode zu dem darin enthaltenen Rückruf hinzu, um anzuzeigen, dass die Schriftarten geladen wurden. Dies ist jedoch nicht das Ende der Geschichte. Ab diesem Punkt ist es sehr browserabhängig. Meistens funktioniert es einwandfrei, aber manchmal kann es erforderlich sein, einige hundert Millisekunden zu warten oder die Schriftarten an einer anderen Stelle auf der Seite zu verwenden. Ich habe verschiedene Optionen ausprobiert und die einzige Methode, die afaik immer funktioniert, besteht darin, schnell einige Testnachrichten mit der Kombination aus Schriftfamilie und Schriftgröße, die Sie verwenden möchten, in die Zeichenfläche zu zeichnen. Sie können dies mit der gleichen Farbe wie der Hintergrund tun, sodass sie nicht einmal sichtbar sind und es sehr schnell geht. Danach funktionierten Schriftarten immer für mich und in allen Browsern.


0

Meine Antwort bezieht sich eher auf Google Web-Schriftarten als auf @ font-face. Ich habe überall nach einer Lösung für das Problem gesucht, dass die Schrift nicht auf der Leinwand angezeigt wird. Ich habe Timer, setInterval, Font-Delay-Bibliotheken und alle möglichen Tricks ausprobiert. Nichts hat geklappt. (Einschließlich Einfügen der Schriftfamilie in das CSS für Zeichenfläche oder der ID des Zeichenflächenelements.)

Ich fand jedoch, dass das Animieren von Text, der in einer Google-Schriftart gerendert wurde, problemlos funktioniert. Was ist der Unterschied? In der Leinwandanimation zeichnen wir die animierten Elemente immer wieder neu. So kam mir die Idee, den Text zweimal zu rendern.

Das hat auch nicht funktioniert - bis ich auch eine kurze (100ms) Timer-Verzögerung hinzugefügt habe. Ich habe bisher nur auf einem Mac getestet. Chrome funktionierte bei 100 ms einwandfrei. Safari erforderte ein erneutes Laden der Seite, daher erhöhte ich den Timer auf 1000, und dann war es in Ordnung. Firefox 18.0.2 und 20.0 würden nichts auf die Leinwand laden, wenn ich Google-Schriftarten (einschließlich der Animationsversion) verwenden würde.

Vollständiger Code: http://www.macloo.com/examples/canvas/canvas10.html

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


0

Steht vor dem gleichen Problem. Nachdem ich "bobince" und andere Kommentare gelesen habe, verwende ich folgendes Javascript, um es zu umgehen:

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

0

Wenn Sie jedes Mal neu zeichnen möchten, wenn eine neue Schriftart geladen wird (und wahrscheinlich das Rendering ändern), hat die API zum Laden von Schriftarten auch dafür ein schönes Ereignis . Ich hatte Probleme mit dem Versprechen in einer vollständig dynamischen Umgebung.

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

Die Leinwand wird unabhängig vom Laden des DOM gezeichnet. Die Vorladetechnik funktioniert nur, wenn die Leinwand nach dem Vorladen gezeichnet wird.

Meine Lösung, auch wenn es nicht die beste ist:

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

Fügen Sie eine Verzögerung wie unten hinzu

<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>
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.