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


97

Когда я рисую текст на холсте шрифтом, который загружается через @ 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
Браузеры загружают шрифт в фоновом режиме асинхронно. Это нормальное поведение. См. Также paulirish.com/2009/fighting-the-font-face-fout
Томас

Ответы:


74

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

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

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

3
У вас может возникнуть соблазн добавить display: none, но из-за этого браузеры могут пропустить загрузку шрифта. Лучше использовать пробел вместо ..
Thomas

6
Использование пробела приведет к тому, что IE отбросит узел пробела, который должен быть в div, не оставив текста для отображения в шрифте. Конечно, IE еще не поддерживает холст, поэтому неизвестно, продолжит ли IE в будущем делать это и повлияет ли это на поведение при загрузке шрифтов, но это давняя проблема IE HTML-парсинга.
bob

1
Нет более простого способа предварительно загрузить шрифт? например, как-то заставить его через javascript?
Джошуа

@Joshua: только создав элемент на странице и установив на нем шрифт, т.е. создание того же контента, что и выше, но динамически.
bobince

17
Добавление этого не гарантирует, что шрифт уже будет загружен при выполнении JavaScript. Мне пришлось выполнить свой скрипт, используя отложенный шрифт (setTimeout), что, конечно, плохо.
Ник,

23

Используйте этот трюк и привяжите 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 /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
хитрый трюк. обратите внимание, что вы загружаете CSS, который, в свою очередь, содержит ссылку на настоящий файл шрифта (например, .ttf, .woff и т. д.). Мне пришлось использовать ваш трюк дважды, один раз для файла css и один раз для файла шрифта, на который есть ссылка (.woff), чтобы убедиться, что все загружено.
magma

1
Пробовал этот подход с шрифтом .ttf - не работает стабильно в Chrome (41.0.2272.101 м). Даже setTimeout за 5 секунд не помогает - первый рендер идет со шрифтом по умолчанию.
Михаил Фядосенко

2
Перед установкой src вы должны установить обработчик ошибок onerror
asdjfiasd

1
также «новое изображение»; отсутствует скобка.
asdjfiasd 08

@asdjfiasd Скобки не требуются, и, хотя srcатрибут установлен, загрузка начнется в следующем блоке выполнения.
оплачиваемый ботаник

16

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

Посмотрите на 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);
  })();

14

Вы можете загружать шрифты с помощью 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 является рабочим проектом на момент написания этой статьи. См. Таблицу совместимости браузеров здесь.


1
Вы пропали document.fonts.add. См. Ответ Бруно.
Pacerier

Спасибо, @Pacerier! Обновил свой ответ.
Фред Бергман

Эта технология является экспериментальной и не поддерживается большинством браузеров.
Михаил Зеленский

11

Как насчет использования простого 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>


3

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

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

холст {семейство шрифтов: PressStart; }


3
Похоже, не работает: протестировано в Chrome 12 Изменить: обновите несколько раз, чтобы не было шрифта
Райан Бадур

1

Я не уверен, поможет ли это вам, но чтобы решить проблему с моим кодом, я просто создал цикл 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);
    }

Я добавил код выше, чтобы проиллюстрировать свой ответ. У меня была аналогичная проблема при разработке другой веб-страницы, и это решило ее, поскольку на стороне сервера он загружает все шрифты, что позволяет им правильно отображаться на веб-странице.
grapien

1

Я написал 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 для демонстрации.


1

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


1

В этой статье я разобрался с моими проблемами с не отображаемыми лениво загруженными шрифтами.

Как загружать веб-шрифты, чтобы избежать проблем с производительностью и ускорить загрузку страницы

Это помогло мне ...

    <link rel="preload" as="font" href="assets/fonts/Maki2/fontmaki2.css" rel="stylesheet" crossorigin="anonymous">

0

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


0

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

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

Это тоже не сработало - пока я не добавил короткую (100 мс) задержку таймера. Пока тестировал только на Mac. Хром нормально работал на 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


0

Столкнулся с той же проблемой. Прочитав «bobince» и другие комментарии, я использую следующий javascript, чтобы обойти это:

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

0

Если вы хотите перерисовывать каждый раз при загрузке нового шрифта (и, возможно, изменять рендеринг), у api загрузки шрифта есть хорошее событие для этого. У меня были проблемы с 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
}

0

Холст отрисовывается независимо от загрузки 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();
}


-4

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

<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>
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.