Рисование текста в <холст> с помощью @font-face не работает в первый раз

Когда я рисую текст в холсте с помощью шрифта, загружаемого через @font-face, текст отображается неправильно. Он вообще не отображается (в Chrome 13 и Firefox 5), или шрифт неправильный (Opera 11). Этот тип неожиданного поведения происходит только на первом рисунке с помощью шрифта. После этого все работает нормально.

Это стандартное поведение или что-то еще?

Спасибо.

PS: Ниже приведен исходный код тестового примера

<!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

Рисование на холсте должно произойти и немедленно вернуться, когда вы вызываете метод fillText. Однако браузер еще не загрузил шрифт из сети, что является фоновой задачей. Поэтому он должен вернуться к шрифту, который у него имеется.

Если вы хотите удостовериться, что шрифт доступен, у вас есть другой элемент на странице, например:

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

Ответ 2

Используйте этот трюк и привяжите событие onerror к элементу Image.

Демо здесь: работает с последним 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 /questions/73200/javascript-capturing-load-event-on-link
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

Проблема заключается в том, что вы пытаетесь использовать шрифт, но браузер еще не загрузил его и, возможно, даже не запросил его. Вам нужно что-то, что загрузит шрифт и даст вам обратный вызов после его загрузки; как только вы получите обратный вызов, вы знаете, что это нормально использовать шрифт.

Посмотрите на Google WebFont Loader; он выглядит как "пользовательский" поставщик и обратный вызов active после того, как загрузка заставит его работать.

Я никогда не использовал его раньше, но из быстрого сканирования документов вам нужно сделать css файл fonts/pressstart2p.css, например:

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

Затем добавьте следующий 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);
  })();

Ответ 4

Как насчет того, чтобы использовать простой CSS, чтобы скрыть div, используя этот шрифт следующим образом:

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>

Ответ 5

Вы можете загружать шрифты с помощью FontFace API, прежде чем использовать его в холсте:

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

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

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

Сначала загружается ресурс шрифта myfont.woff2. После завершения загрузки шрифт добавляется в документ FontFaceSet.

Спецификация FontFace API - это рабочий проект на момент написания этой статьи. См. таблицу совместимости браузеров здесь.

Ответ 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');

});

Ответ 7

Я столкнулся с проблемой, когда играл с ней в последнее время http://people.opera.com/patrickl/experiments/canvas/scroller/

работал вокруг него, добавляя шрифт-семейство к холсту непосредственно в CSS, поэтому вы можете просто добавить

canvas {font-family: PressStart; }

Ответ 8

Я не уверен, поможет ли это вам, но для решения проблемы с моим кодом я просто создал цикл for в верхней части моего Javascript, который просматривал все шрифты, которые я хотел загрузить. Затем я запустил функцию, чтобы очистить холст и предварительно загрузить элементы, которые я хотел на холсте. До сих пор он работал отлично. Это была моя логика, я опубликовал свой код ниже:

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

Ответ 9

Я написал jsfiddle, включающий большинство предлагаемых исправлений, но никто не решил проблему. Тем не менее, я новичок программист, поэтому, возможно, не правильно сформулировал предложенные исправления:

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

Обход Я в конце концов сдался и, при первом загрузке, использовал изображение текста, а также позиционировал текст со шрифтами за пределами области отображения холста. Все последующие отображения шрифтов в области отображения холста не сработали. Это не изящное обходное решение любыми способами.

Решение запечено на моем сайте, но если кому-то нужно, я попытаюсь создать jsfiddle для демонстрации.

Ответ 10

Некоторые браузеры поддерживают спецификацию шрифтов CSS. Это позволяет вам зарегистрировать регистрацию обратного вызова, когда все шрифты были загружены. Вы можете отложить рисование своего холста (или, по крайней мере, рисование текста в холст) до тех пор, и вызвать повторную переделку после того, как будет доступен шрифт.

Ответ 11

Прежде всего используйте загрузчик Google Web Font, как было сказано в другом ответе, и добавьте свой код чертежа в обратный вызов, который он предоставляет, чтобы указать, что шрифты были загружены. Однако это еще не конец истории. С этого момента он зависит от браузера. В большинстве случаев он будет работать нормально, но иногда может потребоваться ждать пару сотен миллисекунд или использовать шрифты где-то еще на странице. Я пробовал разные параметры, и один метод, который всегда работает afaik, - это быстро нарисовать тестовые сообщения на холсте с помощью семейств шрифтов и комбинаций размеров шрифтов, которые вы собираетесь использовать. Вы можете сделать это с тем же цветом, что и фон, поэтому они даже не будут видны, и это произойдет очень быстро. После этого шрифты всегда работали на меня и во всех браузерах.

Ответ 12

Лишь такая же проблема. После прочтения "bobince" и других комментариев, я использую следующий javascript для его обхода:

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

Ответ 13

Мой ответ касается шрифтов Google Web, а не @font-face. Я искал везде решение проблемы с шрифтом, который не отображается на холсте. Я пробовал таймеры, setInterval, библиотеки задержки шрифтов и всевозможные трюки. Ничего не получилось. (Включая класть шрифт в CSS для холста или идентификатор элемента canvas.)

Однако я обнаружил, что анимационный текст, отображаемый в шрифте Google, работал легко. Какая разница? В анимации холста мы снова и снова повторяем анимированные объекты. Поэтому у меня появилась идея сделать текст дважды.

Это тоже не сработало - пока я не добавил короткую задержку таймера (100 мс). До сих пор я тестировал только на Mac. Chrome работал нормально на 100 мс. Safari требовал перезагрузки страницы, поэтому я увеличил таймер до 1000, и тогда все было нормально. Firefox 18.0.2 и 20.0 не загружали бы ничего на холст, если бы я использовал шрифты Google (включая версию анимации).

Полный код: http://www.macloo.com/examples/canvas/canvas10.html

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

Ответ 14

Если вы хотите перерисовывать каждый раз, когда загружается новый шрифт (и, вероятно, меняют рендеринг), загрузка шрифта api имеет приятный event для этого тоже. У меня были проблемы с Promise в полной динамической среде.

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
}

Ответ 15

Холст рисуется независимо от загрузки DOM. Техника предварительной загрузки будет работать, только если холст нарисован после предварительной загрузки.

Мое решение, даже если оно не лучшее:

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

Ответ 17

Добавьте задержку ниже

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