Disegnare il testo su <canvas> con @ font-face non funziona la prima volta


93

Quando disegno un testo in una tela con un carattere tipografico caricato tramite @ font-face, il testo non viene visualizzato correttamente. Non viene visualizzato affatto (in Chrome 13 e Firefox 5) o il carattere tipografico è sbagliato (Opera 11). Questo tipo di comportamento imprevisto si verifica solo al primo disegno con il carattere tipografico. Dopodiché tutto funziona bene.

È il comportamento standard o qualcosa del genere?

Grazie.

PS: Di seguito è riportato il codice sorgente del test case

<!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
I browser caricano il carattere in background, in modo asincrono. Questo è un comportamento normale. Vedi anche paulirish.com/2009/fighting-the-font-face-fout
Thomas

Risposte:


71

Il disegno su tela deve avvenire e tornare immediatamente quando chiami il fillTextmetodo. Tuttavia, il browser non ha ancora caricato il carattere dalla rete, che è un'attività in background. Così deve ricadere il tipo di carattere che non hanno a disposizione.

Se vuoi assicurarti che il carattere sia disponibile, fai in modo che qualche altro elemento sulla pagina lo precarichi, ad esempio:

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

3
Potresti essere tentato di aggiungere display: none, ma ciò potrebbe far sì che i browser saltino il caricamento del carattere. È meglio usare uno spazio invece di un file ..
Thomas

6
L'uso di uno spazio farà sì che IE elimini il nodo di spazi vuoti che dovrebbe essere nel div, senza lasciare testo da visualizzare nel carattere. Ovviamente IE non supporta ancora canvas, quindi non è noto se future-IE continuerà a farlo e se ciò avrebbe un effetto sul comportamento di caricamento dei caratteri, ma è un problema di analisi HTML di IE di vecchia data.
bobince

1
Non esiste un modo più semplice per precaricare il carattere? es. forzarlo in qualche modo tramite javascript?
Joshua

@ Joshua: solo creando un elemento sulla pagina e impostando il carattere su di esso, ad es. creando lo stesso contenuto di cui sopra ma dinamicamente.
bobince

17
L'aggiunta di questo non garantisce che il carattere sarà già caricato quando viene eseguito JavaScript. Ho dovuto eseguire il mio script utilizzando il carattere in questione ritardato (setTimeout) che, ovviamente, è un male.
Nick

23

Usa questo trucco e associa un onerrorevento a un Imageelemento.

Demo qui : funziona sull'ultimo 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
trucco intelligente. nota però che stai caricando il css che, a sua volta, contiene un riferimento al file del font reale (es. .ttf, .woff, ecc.). Ho dovuto usare il tuo trucco due volte, una per il file css e una per il file del font di riferimento (.woff) per assicurarmi che tutto fosse caricato.
magma

1
Ho provato questo approccio con il carattere .ttf: non funziona in modo stabile su Chrome (41.0.2272.101 m). Anche il setTimeout in 5 secondi non aiuta: il primo rendering va con il carattere predefinito.
Mikhail Fiadosenka

2
È necessario impostare il gestore di errori prima di impostare src
asdjfiasd

1
anche "nuova immagine"; ha parentesi mancanti.
asdjfiasd

@asdjfiasd Le parentesi non sono richieste e, anche se l' srcattributo è impostato, il caricamento inizierà nel successivo blocco di esecuzione.
un nerd pagato l'

16

Il nocciolo del problema è che stai cercando di utilizzare il carattere ma il browser non lo ha ancora caricato e forse non lo ha nemmeno richiesto. Quello di cui hai bisogno è qualcosa che carichi il carattere e ti dia un callback una volta caricato; una volta ottenuto il callback, sai che va bene usare il carattere.

Guarda il WebFont Loader di Google ; sembra che un provider "personalizzato" e un activecallback dopo il caricamento lo farebbero funzionare.

Non l'ho mai usato prima, ma da una rapida scansione dei documenti è necessario creare un file css fonts/pressstart2p.css, come questo:

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

Quindi aggiungi il seguente 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

Puoi caricare i caratteri con l' API FontFace prima di utilizzarlo nell'area di disegno :

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

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

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

La risorsa del carattere myfont.woff2viene prima scaricata. Al termine del download, il carattere viene aggiunto al FontFaceSet del documento .

La specifica dell'API FontFace è una bozza funzionante al momento della stesura di questo documento. Vedi la tabella di compatibilità del browser qui.


1
Ti manca document.fonts.add. Vedi la risposta di Bruno.
Pacerier

Grazie @Pacerier! Ho aggiornato la mia risposta.
Fred Bergman

Questa tecnologia è sperimentale e non è supportata dalla maggior parte dei browser.
Michael Zelensky

11

Che ne dici di usare un semplice CSS per nascondere un div usando il carattere in questo modo:

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

Non sono sicuro se questo ti aiuterà, ma per risolvere il problema con il mio codice ho semplicemente creato un ciclo for nella parte superiore del mio Javascript che ha eseguito tutti i caratteri che volevo caricare. Quindi ho eseguito una funzione per cancellare la tela e precaricare gli elementi che volevo sulla tela. Finora ha funzionato perfettamente. Questa era la mia logica, ho pubblicato il mio codice di seguito:

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

Ho aggiunto del codice sopra per illustrare la mia risposta. Ho avuto un problema simile durante lo sviluppo di un'altra pagina Web e questo lo ha risolto poiché sul lato server carica tutti i caratteri, quindi consente loro di essere visualizzati correttamente sulla pagina Web.
grapien

1

Ho scritto un jsfiddle che incorpora la maggior parte delle correzioni suggerite qui, ma nessuna ha risolto il problema. Tuttavia, sono un programmatore alle prime armi, quindi forse non ho codificato correttamente le correzioni suggerite:

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

Soluzione alla fine ho ceduto e, al primo caricamento, ho utilizzato un'immagine del testo mentre posizionavo il testo con le facce dei caratteri all'esterno dell'area di visualizzazione della tela. Tutte le successive visualizzazioni dei caratteri all'interno dell'area di visualizzazione della tela non hanno funzionato senza problemi. Questa non è una soluzione elegante in alcun modo.

La soluzione è integrata nel mio sito Web, ma se qualcuno ne ha bisogno proverò a creare un jsfiddle per dimostrare.


1

Alcuni browser supportano la specifica di caricamento dei caratteri CSS . Consente di registrare una richiamata per quando tutti i caratteri sono stati caricati. Puoi ritardare il disegno della tua tela (o almeno disegnare il testo nella tua tela) fino ad allora e attivare un ridisegno una volta che il carattere è disponibile.


0

Prima di tutto usa il caricatore di caratteri Web di Google come consigliato nell'altra risposta e aggiungi il tuo codice di disegno al callback che fornisce per indicare che i caratteri sono stati caricati. Tuttavia questa non è la fine della storia. Da questo punto dipende molto dal browser. Il più delle volte funzionerà bene, ma a volte potrebbe essere necessario attendere un paio di centinaia di millisecondi o utilizzare i caratteri da qualche altra parte sulla pagina. Ho provato diverse opzioni e l'unico metodo che afaik funziona sempre è disegnare rapidamente alcuni messaggi di prova nell'area di disegno con la famiglia di caratteri e le combinazioni di dimensioni dei caratteri che intendi utilizzare. Puoi farlo con lo stesso colore dello sfondo, quindi non saranno nemmeno visibili e avverrà molto velocemente. Dopo di che i caratteri hanno sempre funzionato per me e in tutti i browser.


0

La mia risposta riguarda i caratteri Web di Google piuttosto che @ font-face. Ho cercato ovunque una soluzione al problema del carattere non visualizzato nella tela. Ho provato timer, setInterval, librerie font-delay e tutti i tipi di trucchi. Niente ha funzionato. (Incluso l'inserimento della famiglia di caratteri nel CSS per la tela o l'ID dell'elemento tela.)

Tuttavia, ho scoperto che l' animazione del testo reso con un carattere di Google funzionava facilmente. Qual è la differenza? Nell'animazione su tela, ridisegniamo gli elementi animati ancora e ancora. Quindi ho avuto l'idea di rendere il testo due volte.

Neanche questo ha funzionato, fino a quando non ho aggiunto un breve ritardo del timer (100 ms). Finora ho provato solo su un Mac. Chrome ha funzionato bene a 100 ms. Safari richiedeva un ricaricamento della pagina, quindi ho aumentato il timer a 1000, e poi è andato tutto bene. Firefox 18.0.2 e 20.0 non caricavano nulla sulla tela se stavo usando i caratteri di Google (inclusa la versione di animazione).

Codice completo: http://www.macloo.com/examples/canvas/canvas10.html

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


0

Affronta lo stesso problema. Dopo aver letto "bobince" e altri commenti, utilizzo il seguente javascript per aggirare il problema:

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

0

Se si vuole ridisegnare ogni volta che un nuovo tipo di carattere viene caricato (e probabilmente modificare il rendering) il carico api carattere ha un bel evento anche per questo. Ho avuto problemi con il Promise in un ambiente dinamico completo.

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

La tela viene disegnata indipendentemente dal caricamento del DOM. La tecnica del precarico funzionerà solo se la tela viene disegnata dopo il precarico.

La mia soluzione, anche se non è la migliore:

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

Aggiungi un ritardo come di seguito

<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>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.