Dibujar texto en <canvas> con @ font-face no funciona la primera vez


93

Cuando dibujo un texto en un lienzo con un tipo de letra que se carga a través de @ font-face, el texto no se muestra correctamente. No se muestra en absoluto (en Chrome 13 y Firefox 5), o el tipo de letra es incorrecto (Opera 11). Este tipo de comportamiento inesperado ocurre solo en el primer dibujo con el tipo de letra. Después de eso, todo funciona bien.

¿Es el comportamiento estándar o algo así?

Gracias.

PD: A continuación se muestra el código fuente del caso de prueba.

<!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
Los navegadores cargan la fuente en segundo plano, de forma asincrónica. Este es un comportamiento normal. Véase también paulirish.com/2009/fighting-the-font-face-fout
Thomas

Respuestas:


71

El dibujo en lienzo tiene que suceder y regresar inmediatamente cuando llama al fillTextmétodo. Sin embargo, el navegador aún no ha cargado la fuente de la red, lo cual es una tarea en segundo plano. Así que tiene que caer de nuevo a la fuente que no tenga disponible.

Si desea asegurarse de que la fuente esté disponible, cargue algún otro elemento en la página, por ejemplo:

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

3
Es posible que tenga la tentación de agregar display: none, pero eso puede hacer que los navegadores omitan la carga de la fuente. Es mejor usar un espacio en lugar de un ..
Thomas

6
El uso de un espacio hará que IE elimine el nodo de espacio en blanco que debería estar en el div, sin dejar texto para representar en la fuente. Por supuesto, IE aún no es compatible con el lienzo, por lo que se desconoce si el futuro IE continuará haciendo esto y si eso tendría un efecto en el comportamiento de carga de fuentes, pero es un problema de análisis de HTML de IE de larga data.
Bobince

1
¿No hay una forma más fácil de precargar la fuente? por ejemplo, forzarlo a través de javascript de alguna manera?
Joshua

@Joshua: solo creando un elemento en la página y estableciendo la fuente en él, es decir. creando el mismo contenido que el anterior pero de forma dinámica.
Bobince

17
Agregar esto no garantiza que la fuente ya esté cargada cuando se ejecute JavaScript. Tuve que ejecutar mi script usando la fuente en cuestión demorada (setTimeout) que, por supuesto, es mala.
Nick

23

Utilice este truco y vincule un onerrorevento a un Imageelemento.

Demostración aquí : funciona en la última versión 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);
};

3
truco inteligente. Sin embargo, tenga en cuenta que está cargando el CSS que, a su vez, contiene una referencia al archivo de fuente real (por ejemplo, .ttf, .woff, etc.). Tuve que usar su truco dos veces, una para el archivo css y otra para el archivo de fuente referenciado (.woff) para asegurarme de que todo estaba cargado.
magma

1
Probé este enfoque con la fuente .ttf; no funciona de manera estable en Chrome (41.0.2272.101 m). Incluso el setTimeout en 5 segundos no ayuda: el primer renderizado va con la fuente predeterminada.
Mikhail Fiadosenka

2
Debe configurar un controlador de error antes de configurar src
asdjfiasd

1
también "nueva imagen"; le faltan paréntesis.
asdjfiasd

@asdjfiasd Los parens no son obligatorios y, aunque el srcatributo esté configurado, la carga comenzará en el siguiente bloque de ejecución.
un nerd pagado

16

El meollo del problema es que está intentando utilizar la fuente pero el navegador aún no la ha cargado y posiblemente ni siquiera la haya solicitado. Lo que necesita es algo que cargue la fuente y le devuelva la llamada una vez que esté cargada; una vez que reciba la devolución de llamada, sabrá que está bien usar la fuente.

Mire WebFont Loader de Google ; parece un proveedor "personalizado" y una activedevolución de llamada después de la carga lo haría funcionar.

Nunca lo había usado antes, pero a partir de un escaneo rápido de los documentos, necesita hacer un archivo css fonts/pressstart2p.css, como este:

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

Luego agregue el siguiente 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

Puede cargar fuentes con la API FontFace antes de usarlas en el lienzo:

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

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

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

myfont.woff2Primero se descarga el recurso de fuente . Una vez que se completa la descarga, la fuente se agrega al FontFaceSet del documento .

La especificación de la API de FontFace es un borrador de trabajo en el momento de escribir este artículo. Consulte la tabla de compatibilidad de navegadores aquí.


1
Te estás perdiendo document.fonts.add. Vea la respuesta de bruno.
Pacerier

¡Gracias @Pacerier! Actualicé mi respuesta.
Fred Bergman

Esta tecnología es experimental y no es compatible con la mayoría de los navegadores.
Michael Zelensky

11

¿Qué hay de usar CSS simple para ocultar un div usando una fuente como esta?

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

No estoy seguro de si esto te ayudará, pero para resolver el problema con mi código, simplemente creé un bucle for en la parte superior de mi Javascript que pasaba por todas las fuentes que quería cargar. Luego ejecuté una función para borrar el lienzo y precargar los elementos que quería en el lienzo. Hasta ahora ha funcionado perfectamente. Esa fue mi lógica, he publicado mi código a continuación:

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

Agregué un código arriba para ilustrar mi respuesta. Tuve un problema similar al desarrollar otra página web y esto lo resolvió ya que en el extremo del servidor carga todas las fuentes, por lo que permite que se muestren correctamente en la página web.
grapien

1

Escribí un jsfiddle incorporando la mayoría de las correcciones sugeridas aquí, pero ninguna resolvió el problema. Sin embargo, soy un programador novato, por lo que tal vez no codifiqué las correcciones sugeridas correctamente:

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

Solución alterna Finalmente cedí y, en la primera carga, usé una imagen del texto mientras también colocaba el texto con las fuentes fuera del área de visualización del lienzo. Todas las visualizaciones posteriores de las fuentes dentro del área de visualización del lienzo funcionaron sin problemas. Esta no es una solución elegante de ninguna manera.

La solución está integrada en mi sitio web, pero si alguien la necesita, intentaré crear un jsfiddle para demostrarlo.


1

Algunos navegadores admiten la especificación de carga de fuentes CSS . Le permite registrar una devolución de llamada para cuando se hayan cargado todas las fuentes. Puede retrasar el dibujo de su lienzo (o al menos dibujar texto en su lienzo) hasta entonces y activar un nuevo dibujo una vez que la fuente esté disponible.


0

En primer lugar, use el cargador de fuentes web de Google como se recomienda en la otra respuesta y agregue su código de dibujo a la devolución de llamada que proporciona para indicar que las fuentes se han cargado. Sin embargo, este no es el final de la historia. Desde este punto, depende mucho del navegador. La mayoría de las veces funcionará bien, pero a veces puede ser necesario esperar unos cientos de milisegundos o usar las fuentes en otro lugar de la página. Probé diferentes opciones y el único método que afaik siempre funciona es dibujar rápidamente algunos mensajes de prueba en el lienzo con la familia de fuentes y las combinaciones de tamaño de fuente que vas a usar. Puedes hacerlo con el mismo color que el fondo, por lo que ni siquiera serán visibles y sucederá muy rápido. Después de eso, las fuentes siempre funcionaron para mí y en todos los navegadores.


0

Mi respuesta aborda las fuentes web de Google en lugar de @ font-face. Busqué por todas partes una solución al problema de la fuente que no aparecía en el lienzo. Probé temporizadores, setInterval, bibliotecas de retardo de fuentes y todo tipo de trucos. Nada funcionó. (Incluyendo poner font-family en el CSS para el lienzo o el ID del elemento del lienzo).

Sin embargo, descubrí que la animación de texto renderizado en una fuente de Google funcionaba fácilmente. ¿Cual es la diferencia? En la animación de lienzo, volvemos a dibujar los elementos animados una y otra vez. Entonces tuve la idea de renderizar el texto dos veces.

Eso tampoco funcionó, hasta que también agregué un breve retardo de temporizador (100 ms). Solo lo he probado en una Mac hasta ahora. Chrome funcionó bien a 100 ms. Safari requirió una recarga de página, así que aumenté el temporizador a 1000, y luego estuvo bien. Firefox 18.0.2 y 20.0 no cargarían nada en el lienzo si estuviera usando fuentes de Google (incluida la versión de animación).

Código completo: http://www.macloo.com/examples/canvas/canvas10.html

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


0

Se enfrenta al mismo problema. Después de leer "bobince" y otros comentarios, utilizo el siguiente javascript para solucionarlo:

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

0

Si desea volver a dibujar cada vez que se carga una nueva fuente (y probablemente cambiar la representación), la API de carga de fuentes también tiene un buen evento para eso. Tuve problemas con la Promesa en un entorno completamente dinámico.

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

El lienzo se dibuja independientemente de la carga DOM. La técnica de precarga solo funcionará si el lienzo se dibuja después de la precarga.

Mi solución, aunque no sea la mejor:

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

Agregue un retraso como se muestra a continuación

<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>
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.