web-dev-qa-db-fra.com

Le dessin de texte sur <canvas> avec @ font-face ne fonctionne pas pour la première fois

Lorsque je dessine un texte dans un canevas avec une police de caractères 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 de caractères est incorrecte (Opera 11). Ce type de comportement inattendu se produit uniquement lors du premier dessin avec la police de caractères. Après cela, tout fonctionne bien.

Est-ce le comportement standard ou quelque chose?

Merci.

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>
83
lemonedo

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

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

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

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

Démo ici : fonctionne sur le dernier 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);
};
20
a paid nerd

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. Vous avez besoin 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 Google WebFont Loader ; il semble être un fournisseur "personnalisé" et un rappel active après le chargement le ferait fonctionner.

Je ne l'ai jamais utilisé auparavant, mais à partir d'une analyse rapide des documents, vous devez créer un fichier css fonts/pressstart2p.css, comme ça:

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

Qu'en est-il de l'utilisation de CSS simple pour masquer un div en utilisant la police comme ceci:

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>
10
Gabriel Gonzalez

Vous pouvez charger des polices avec FontFace API avant de les 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.woff2 est d'abord téléchargé. Une fois le téléchargement terminé, la police est ajoutée à FontFaceSet du document.

La spécification de l'API FontFace est un brouillon de travail au moment d'écrire ces lignes. Voir le tableau de compatibilité du navigateur ici.

7
Fred Bergman

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

});
4
Bruno

j'ai rencontré le problème lors de la lecture récente http://people.opera.com/patrickl/experiments/canvas/scroller/

travaillé en ajoutant la famille de polices au canevas directement dans le CSS, vous pouvez donc simplement ajouter

canvas {famille de polices: PressStart; }

3

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 débutant, alors 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 à l'extérieur du canevas zone d'affichage. Tous les affichages suivants des polices-faces dans la zone d'affichage du canevas n'ont fonctionné aucun problème. Ce n'est en aucun cas une solution de contournement élégante.

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

1
HatHead

Certains support des navigateurs la spécification CSS Font Loading . Il vous permet d'enregistrer, 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 nouveau dessin une fois que la police est disponible.

1
MvG

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 le canevas et précharger les éléments que je voulais sur le canevas. 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);
    }
1
grapien

J'essaie d'utiliser FontFaceSet.load pour résoudre le problème: https://jsfiddle.net/wengshenshun/gr1zkvtq/

const prepareFontLoad = (fontList) => Promise.all(fontList.map(font => document.fonts.load(font)))

Vous pouvez trouver la compatibilité du navigateur sur https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/load

0
翁沈顺

Ma réponse concerne les polices Web de Google 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é les minuteries, setInterval, les bibliothèques de retard de police et toutes sortes de trucs. Rien n'a fonctionné. (Y compris mettre la famille de polices dans le CSS pour canvas ou l'ID de l'élément canvas.)

Cependant, j'ai trouvé que le texte animé 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'ajoute également un court délai de temporisation (100 ms). Jusqu'à présent, je n'ai testé que sur un Mac. Chrome fonctionnait bien à 100 ms. Safari nécessitait un rechargement de la page, j'ai donc augmenté le minuteur à 1000, puis ça allait. Firefox 18.0.2 et 20.0 ne chargeraient rien sur la toile 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

0
macloo

Si vous souhaitez redessiner à chaque fois qu'une nouvelle police est chargée (et probablement changer le rendu), l'api de chargement de police a aussi un Nice événement pour cela. J'ai eu des problèmes avec la 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
}
0
HolgerJeromin

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. Mais ce n'est pas la fin de l'histoire. À partir de ce moment, cela 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 qui fonctionne toujours afaik consiste à dessiner rapidement des messages de test dans le canevas avec la famille de polices et les combinaisons de tailles 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 que cela se produira très rapidement. Après cela, les polices fonctionnaient toujours pour moi et dans tous les navigateurs.

0
Megas

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

Ma solution, même si elle 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();
}
0
Eduardo Mihalache

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

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