Dessiner du texte sur <canvas> avec @ font-face ne fonctionne pas la première fois

93

Lorsque je dessine un texte dans un canevas avec une police qui est chargée via @ font-face, le texte ne s'affiche pas correctement. Il ne s'affiche pas du tout (dans Chrome 13 et Firefox 5), ou la police est incorrecte (Opera 11). Ce type de comportement inattendu se produit uniquement lors du premier dessin avec la police. Ensuite, tout fonctionne bien.

Est-ce le comportement standard ou quelque chose?

Je vous remercie.

PS: voici le code source du cas de test

<!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>
limonède
la source
1
Les navigateurs chargent la police en arrière-plan, de manière asynchrone. C'est un comportement normal. Voir aussi paulirish.com/2009/fighting-the-font-face-fout
Thomas

Réponses:

71

Le dessin sur toile doit se produire et revenir immédiatement lorsque vous appelez la fillTextméthode. Cependant, le navigateur n'a pas encore chargé la police depuis le réseau, ce qui est une tâche d'arrière-plan. Il doit donc revenir à la police dont il dispose .

Si vous voulez vous assurer que la police est disponible, faites précharger un autre élément sur la page, par exemple:

<div style="font-family: PressStart;">.</div>
bobince
la source
3
Vous pourriez être tenté d'ajouter display: none, mais cela pourrait empêcher les navigateurs de charger la police. Il vaut mieux utiliser un espace au lieu d'un ..
Thomas
6
L'utilisation d'un espace entraînera IE à jeter le nœud d'espace blanc qui devrait être dans le div, ne laissant aucun texte à rendre dans la police. Bien sûr, IE ne prend pas encore en charge le canevas, donc on ne sait pas si future-IE continuera à le faire, et si cela aurait un effet sur le comportement de chargement des polices, mais c'est un problème de longue date d'analyse HTML d'IE.
bobince
1
Existe-t-il un moyen plus simple de précharger la police? par exemple forcer à travers javascript en quelque sorte?
Joshua
@Joshua: uniquement en créant un élément sur la page et en définissant la police dessus, ie. créer le même contenu que ci-dessus mais de manière dynamique.
bobince
17
L'ajout de cela ne garantit pas que la police sera déjà chargée lors de l'exécution du JavaScript. J'ai dû exécuter mon script en utilisant la police en question retardée (setTimeout) qui, bien sûr, est mauvaise.
Nick
23

Utilisez cette astuce et liez un onerrorévénement à un Imageélément.

Démo ici : fonctionne sur la dernière version de 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);
};
un nerd payé
la source
3
astuce intelligente. notez cependant que vous chargez le css qui, à son tour, contient une référence au fichier de police réel (par exemple, .ttf, .woff, etc.). J'ai dû utiliser votre astuce deux fois, une fois pour le fichier css et une fois pour le fichier de polices référencé (.woff) pour m'assurer que tout était chargé.
magma
1
J'ai essayé cette approche avec la police .ttf - ne fonctionne pas de manière stable sur Chrome (41.0.2272.101 m). Même le setTimeout en 5 secondes n'aide pas - le premier rendu va avec la police par défaut.
Mikhail Fiadosenka
2
Vous devez définir un gestionnaire d'erreur avant de définir src
asdjfiasd
1
également "nouvelle image;" a une parenthèse manquante.
asdjfiasd
@asdjfiasd Les parenthèses ne sont pas obligatoires et, même si l' srcattribut est défini, le chargement commencera dans le bloc d'exécution suivant.
un nerd payé le
16

Le nœud du problème est que vous essayez d'utiliser la police mais que le navigateur ne l'a pas encore chargée et ne l'a peut-être même pas demandée. Ce dont vous avez besoin, c'est de quelque chose qui chargera la police et vous rappellera une fois qu'elle sera chargée; une fois que vous obtenez le rappel, vous savez que vous pouvez utiliser la police.

Regardez le WebFont Loader de Google ; il semble qu'un fournisseur "personnalisé" et un activerappel après le chargement le feraient fonctionner.

Je ne l'ai jamais utilisé auparavant, mais à partir d'une analyse rapide de la documentation, vous devez créer un fichier css fonts/pressstart2p.css, comme celui-ci:

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

Ajoutez ensuite le JS suivant:

  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);
  })();
ellisbben
la source
13

Vous pouvez charger des polices avec l' API FontFace avant de l'utiliser dans le canevas:

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

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

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

La ressource de police myfont.woff2est d'abord téléchargée. Une fois le téléchargement terminé, la police est ajoutée au FontFaceSet du document .

La spécification de l'API FontFace est un projet de travail au moment de la rédaction de cet article. Voir le tableau de compatibilité du navigateur ici.

Fred Bergman
la source
1
Vous avez disparu document.fonts.add. Voir la réponse de Bruno.
Pacerier
Merci @Pacerier! Mis à jour ma réponse.
Fred Bergman
Cette technologie est expérimentale et n'est pas prise en charge par la plupart des navigateurs.
Michael Zelensky
11

Qu'en est-il d'utiliser un CSS simple pour masquer un div en utilisant la police comme celle-ci:

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>
Gabriel Gonzalez
la source
6

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

});
Bruno Andrade
la source
1

Je ne sais pas si cela vous aidera, mais pour résoudre le problème avec mon code, j'ai simplement créé une boucle for en haut de mon Javascript qui parcourait toutes les polices que je voulais charger. J'ai ensuite exécuté une fonction pour effacer la toile et précharger les éléments que je voulais sur la toile. Jusqu'à présent, cela a parfaitement fonctionné. C'était ma logique, j'ai posté mon code ci-dessous:

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
la source
J'ai ajouté du code ci-dessus pour illustrer ma réponse. J'ai eu un problème similaire lors du développement d'une autre page Web et cela l'a résolu car à l'extrémité du serveur, il charge toutes les polices, ce qui leur permet de s'afficher correctement sur la page Web.
grapien
1

J'ai écrit un jsfiddle incorporant la plupart des correctifs suggérés ici, mais aucun n'a résolu le problème. Cependant, je suis un programmeur novice, donc je n'ai peut-être pas codé correctement les correctifs suggérés:

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

Solution de contournement J'ai finalement cédé et, lors du premier chargement, j'ai utilisé une image du texte tout en positionnant le texte avec les polices de caractères en dehors de la zone d'affichage du canevas. Tous les affichages suivants des polices de caractères dans la zone d'affichage du canevas ont fonctionné sans problème. Ce n'est en aucun cas une solution de contournement élégante.

La solution est intégrée à mon site Web, mais si quelqu'un en a besoin, j'essaierai de créer un jsfiddle pour le démontrer.

Chapeau
la source
1

Certains navigateurs prennent en charge la spécification CSS Font Loading . Il vous permet d'enregistrer un rappel lorsque toutes les polices ont été chargées. Vous pouvez retarder le dessin de votre canevas (ou au moins dessiner du texte dans votre canevas) jusque-là, et déclencher un redessiner une fois que la police est disponible.

MvG
la source
0

Tout d'abord, utilisez le chargeur de polices Web de Google comme indiqué dans l'autre réponse et ajoutez votre code de dessin au rappel qu'il fournit pour indiquer que les polices ont été chargées. Cependant, ce n'est pas la fin de l'histoire. À partir de là, il dépend beaucoup du navigateur. La plupart du temps, cela fonctionnera bien, mais il peut parfois être nécessaire d'attendre quelques centaines de millisecondes ou d'utiliser les polices ailleurs sur la page. J'ai essayé différentes options et la seule méthode qu'afaik fonctionne toujours est de dessiner rapidement des messages de test dans le canevas avec la famille de polices et les combinaisons de taille de police que vous allez utiliser. Vous pouvez le faire avec la même couleur que l'arrière-plan, de sorte qu'ils ne seront même pas visibles et cela se produira très rapidement. Après cela, les polices ont toujours fonctionné pour moi et dans tous les navigateurs.

Megas
la source
0

Ma réponse concerne les polices Google Web plutôt que @ font-face. J'ai cherché partout une solution au problème de la police n'apparaissant pas dans la toile. J'ai essayé des minuteries, setInterval, des bibliothèques de délai de police et toutes sortes d'astuces. Rien n'a fonctionné. (Y compris mettre la famille de polices dans le CSS pour le canevas ou l'ID de l'élément canevas.)

Cependant, j'ai trouvé que le texte d' animation rendu dans une police Google fonctionnait facilement. Quelle est la différence? Dans l'animation de canevas, nous redessinons les éléments animés encore et encore. J'ai donc eu l'idée de rendre le texte deux fois.

Cela n'a pas fonctionné non plus - jusqu'à ce que j'aie également ajouté un court délai (100 ms). Je n'ai testé que sur un Mac jusqu'à présent. Chrome a bien fonctionné à 100 ms. Safari nécessitait un rechargement de page, j'ai donc augmenté la minuterie à 1000, puis tout allait bien. Firefox 18.0.2 et 20.0 ne chargeraient rien sur le canevas si j'utilisais des polices Google (y compris la version d'animation).

Code complet: http://www.macloo.com/examples/canvas/canvas10.html

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

Macloo
la source
0

Fait face au même problème. Après avoir lu "bobince" et d'autres commentaires, j'utilise le javascript suivant pour contourner le problème:

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

Si vous souhaitez redessiner à chaque fois qu'une nouvelle police est chargée (et probablement modifier le rendu), l'API de chargement de la police a également un événement intéressant pour cela. J'ai eu des problèmes avec le Promise dans un environnement dynamique complet.

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
}
HolgerJeromin
la source
0

Le canevas est dessiné indépendamment du chargement du DOM. La technique de précharge ne fonctionnera que si le canevas est dessiné après le préchargement.

Ma solution, même si ce n'est pas la meilleure:

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();
}
Eduardo Mihalache
la source
-4

Ajoutez un délai comme ci-dessous

<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>
peu importe
la source