Малювання тексту в <canvas> за допомогою @ font-face спочатку не працює


93

Коли я малюю текст на полотні шрифтом, який завантажується через @ 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
Томас

Відповіді:


71

Малювання на полотні має відбутися і негайно повернутися, коли ви викликаєте fillTextметод. Однак браузер ще не завантажив шрифт з мережі, що є фоновим завданням. Тому він повинен впасти назад до шрифту він дійсно є в наявності.

Якщо ви хочете переконатись, що шрифт доступний, додайте на сторінку якийсь інший елемент, попередньо завантажте його, наприклад:

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

3
Можливо, у вас виникне спокуса додати display: none, але це може спричинити пропуск браузерами завантаження шрифту. Краще використовувати пробіл замість ..
Томас

6
Використання пробілу призведе до того, що IE викине пробіл, який повинен бути у div, не залишаючи тексту для відтворення у шрифті. Звичайно, IE ще не підтримує canvas, тому невідомо, чи буде майбутній IE продовжувати це робити, і чи не вплине це на поведінку завантаження шрифтів, але це давня проблема розбору HTML IE.
bobince

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), щоб переконатися, що все завантажено.
магма

1
Спробував такий підхід зі шрифтом .ttf - він не працює стабільно в Chrome (41.0.2272.101 м). Навіть setTimeout за 5 секунд не допомагає - перший рендер йде зі шрифтом за замовчуванням.
Михайло Фядосенка

2
Вам слід встановити обробник onerror перед тим, як встановити src
asdjfiasd

1
також "новий образ;" має відсутні дужки.
asdjfiasd

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

13

Ви можете завантажити шрифти за допомогою 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 документа .

Специфікація API FontFace є робочим проектом на момент написання цієї статті. Див. Таблицю сумісності браузера тут.


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 Edit: Оновіть кілька разів, щоб він пропустив шрифт
Райан Бадур,

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 специфікації. Це дозволяє зареєструвати реєстрацію зворотного дзвінка, коли всі шрифти завантажені. Ви можете відкласти малювання свого полотна (або, принаймні, нанесення тексту на нього) до тих пір, і запустити перемальовування, як тільки шрифт стане доступним.


0

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


0

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

Однак я виявив, що анімація тексту, відтвореного шрифтом 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


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


0

У цій статті розібрано мої проблеми з тим, що ліниві завантажені шрифти не відображаються.

Як завантажувати веб-шрифти, щоб уникнути проблем із продуктивністю та прискорити завантаження сторінки

Це допомогло мені ...

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

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