Menggambar teks ke <canvas> dengan @ font-face tidak berfungsi pada saat pertama

97

Saat saya menggambar teks di kanvas dengan jenis huruf yang dimuat melalui @ font-face, teks tersebut tidak ditampilkan dengan benar. Itu tidak muncul sama sekali (di Chrome 13 dan Firefox 5), atau jenis hurufnya salah (Opera 11). Jenis perilaku tak terduga ini hanya terjadi pada gambar pertama dengan jenis huruf. Setelah itu semuanya bekerja dengan baik.

Apakah ini perilaku standar atau semacamnya?

Terima kasih.

PS: Berikut ini adalah kode sumber dari kasus uji

<!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>
lemonedo
sumber
1
Browser memuat font di latar belakang, secara asinkron. Ini adalah perilaku normal. Lihat juga paulirish.com/2009/fighting-the-font-face-fout
Thomas

Jawaban:

74

Menggambar di kanvas harus terjadi dan segera kembali saat Anda memanggil fillTextmetode tersebut. Namun, browser belum memuat font dari jaringan, yang merupakan tugas latar belakang. Sehingga harus jatuh kembali ke font itu tidak ada.

Jika Anda ingin memastikan font tersedia, buat beberapa elemen lain di halaman pramuat, misalnya:

<div style="font-family: PressStart;">.</div>
bobince
sumber
3
Anda mungkin tergoda untuk menambahkan display: none, tetapi itu mungkin menyebabkan browser melewatkan pemuatan font. Lebih baik menggunakan spasi daripada ..
Thomas
6
Menggunakan spasi akan menyebabkan IE membuang node spasi putih yang seharusnya ada di div, tidak menyisakan teks untuk dirender di font. Tentu saja IE belum mendukung kanvas, jadi tidak diketahui apakah IE-masa depan akan terus melakukan ini, dan apakah itu akan berdampak pada perilaku pemuatan font, tetapi ini adalah masalah penguraian HTML IE yang sudah lama ada.
bobince
1
Apakah tidak ada cara yang lebih mudah untuk melakukan pramuat font? misalnya memaksanya melalui javascript?
Joshua
@Joshua: hanya dengan membuat elemen pada halaman dan menyetel font di atasnya, mis. membuat konten yang sama seperti di atas tetapi secara dinamis.
bobince
17
Menambahkan ini tidak menjamin bahwa font telah dimuat saat JavaScript dijalankan. Saya harus menjalankan skrip saya menggunakan font yang dimaksud tertunda (setTimeout) yang, tentu saja, buruk.
Nick
23

Gunakan trik ini dan ikat onerroracara ke Imageelemen.

Demo di sini : berfungsi di Chrome terbaru.

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);
};
seorang kutu buku yang dibayar
sumber
3
trik cerdas. perhatikan bahwa Anda sedang memuat css yang, pada gilirannya, berisi referensi ke file font asli (mis., .ttf, .woff, dll). Saya harus menggunakan trik Anda dua kali, sekali untuk file css, dan sekali untuk file font yang direferensikan (.woff) untuk memastikan bahwa semuanya telah dimuat.
magma
1
Mencoba pendekatan ini dengan font .ttf - tidak berfungsi secara stabil di Chrome (41.0.2272.101 m). Bahkan setTimeout dalam 5 detik tidak membantu - render pertama menggunakan font default.
Mikhail Fiadosenka
2
Anda harus menyetel penangan onerror sebelum Anda menyetel src
asdjfiasd
1
juga "Gambar baru;" tidak memiliki tanda kurung.
asdjfiasd
@asdjfiasd Tanda kurung tidak diperlukan dan, meskipun srcatribut disetel, pemuatan akan dimulai di blok eksekusi berikutnya.
seorang kutu buku yang dibayar
16

Inti masalahnya adalah Anda mencoba menggunakan font tersebut tetapi browser belum memuatnya dan bahkan mungkin belum memintanya. Yang Anda butuhkan adalah sesuatu yang akan memuat font dan memberi Anda panggilan balik setelah dimuat; setelah Anda mendapatkan panggilan balik, Anda tahu tidak apa-apa menggunakan font tersebut.

Lihat WebFont Loader Google ; sepertinya penyedia "khusus" dan activecallback setelah pemuatan akan membuatnya berfungsi.

Saya belum pernah menggunakannya sebelumnya, tetapi dari pemindaian cepat dokumen Anda perlu membuat file css fonts/pressstart2p.css, seperti ini:

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

Kemudian tambahkan JS berikut:

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

Anda dapat memuat font dengan FontFace API sebelum menggunakannya di kanvas:

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

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

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

Sumber daya font myfont.woff2diunduh pertama kali. Setelah unduhan selesai, font ditambahkan ke FontFaceSet dokumen .

Spesifikasi FontFace API adalah draft kerja pada saat penulisan ini. Lihat tabel kompatibilitas browser di sini.

Fred Bergman
sumber
1
Anda hilang document.fonts.add. Lihat jawaban Bruno.
Pacerier
Terima kasih @Pacerier! Memperbarui jawaban saya.
Fred Bergman
Teknologi ini bersifat eksperimental dan tidak didukung oleh sebagian besar browser.
Michael Zelensky
11

Bagaimana dengan menggunakan CSS sederhana untuk menyembunyikan div menggunakan font seperti ini:

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>
Gabriel Gonzalez
sumber
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');

});
Bruno Andrade
sumber
1

Saya tidak yakin apakah ini akan membantu Anda, tetapi untuk menyelesaikan masalah dengan kode saya, saya hanya membuat loop for di bagian atas Javascript saya yang menjalankan semua font yang ingin saya muat. Saya kemudian menjalankan fungsi untuk membersihkan kanvas dan melakukan pramuat item yang saya inginkan di kanvas. Sejauh ini telah bekerja dengan sempurna. Itulah logika saya, saya telah memposting kode saya di bawah ini:

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.dll
sumber
Saya telah menambahkan beberapa kode di atas untuk menggambarkan jawaban saya. Saya memiliki masalah serupa ketika mengembangkan halaman web lain dan ini menyelesaikannya seperti pada ujung server memuat semua font sehingga memungkinkan mereka untuk ditampilkan dengan benar di halaman web.
grapien
1

Saya menulis jsfiddle yang menggabungkan sebagian besar perbaikan yang disarankan di sini tetapi tidak ada yang menyelesaikan masalah. Namun, saya seorang programmer pemula jadi mungkin tidak mengkodekan perbaikan yang disarankan dengan benar:

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

Solusi Saya akhirnya menyerah dan, pada pemuatan pertama, menggunakan gambar teks sementara juga memposisikan teks dengan font-face di luar area tampilan kanvas. Semua tampilan berikutnya dari font-face dalam area tampilan kanvas bekerja tanpa masalah. Ini bukan solusi yang elegan dengan cara apa pun.

Solusinya dimasukkan ke dalam situs web saya tetapi jika ada yang membutuhkan, saya akan mencoba membuat jsfiddle untuk didemonstrasikan.

HatHead
sumber
1

Beberapa browser mendukung para CSS Font Memuat spesifikasi. Ini memungkinkan Anda untuk mendaftarkan callback ketika semua font telah dimuat. Anda bisa menunda menggambar kanvas Anda (atau setidaknya menggambar teks ke kanvas Anda) sampai saat itu, dan memicu penggambaran ulang setelah font tersedia.

MvG
sumber
0

Pertama-tama gunakan pemuat Font Web Google seperti yang disarankan dalam jawaban lain dan tambahkan kode gambar Anda ke callback yang disediakan untuk menunjukkan font telah dimuat. Namun ini bukanlah akhir dari cerita. Dari titik ini, ini sangat bergantung pada browser. Sebagian besar waktu itu akan berfungsi dengan baik, tetapi terkadang mungkin diperlukan untuk menunggu beberapa ratus milidetik atau menggunakan font di tempat lain pada halaman. Saya mencoba berbagai opsi dan satu metode yang selalu berhasil afaik adalah dengan cepat menggambar beberapa pesan tes di kanvas dengan keluarga font dan kombinasi ukuran font yang akan Anda gunakan. Anda dapat melakukannya dengan warna yang sama seperti latar belakang, sehingga tidak akan terlihat dan akan terjadi dengan sangat cepat. Setelah itu font selalu berfungsi untuk saya dan di semua browser.

Megas
sumber
0

Jawaban saya membahas font Google Web daripada @ font-face. Saya mencari di mana-mana untuk solusi masalah font tidak muncul di kanvas. Saya mencoba pengatur waktu, setInterval, pustaka font-delay, dan segala macam trik. Tidak ada yang berhasil. (Termasuk meletakkan font-family di CSS untuk kanvas atau ID elemen kanvas.)

Namun, saya menemukan bahwa teks animasi yang dirender dalam font Google bekerja dengan mudah. Apa bedanya? Dalam animasi kanvas, kami menggambar ulang item animasi lagi dan lagi. Jadi saya mendapat ide untuk membuat teks dua kali.

Itu juga tidak berhasil - sampai saya juga menambahkan penundaan pengatur waktu singkat (100ms). Sejauh ini saya hanya menguji di Mac. Chrome bekerja dengan baik pada 100ms. Safari membutuhkan pemuatan ulang halaman, jadi saya meningkatkan pengatur waktu menjadi 1000, dan kemudian semuanya baik-baik saja. Firefox 18.0.2 dan 20.0 tidak akan memuat apa pun di kanvas jika saya menggunakan font Google (termasuk versi animasinya).

Kode lengkap: http://www.macloo.com/examples/canvas/canvas10.html

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

macloo
sumber
0

Menghadapi masalah yang sama. Setelah membaca "bobince" dan komentar lainnya, saya menggunakan javascript berikut untuk mengatasinya:

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

Jika Anda ingin menggambar ulang setiap kali font baru dimuat (dan mungkin mengubah rendering), api pemuatan font juga memiliki acara yang bagus untuk itu. Saya memiliki masalah dengan Janji di lingkungan yang sangat dinamis.

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
}
HolgerJeromin
sumber
0

Kanvas digambar secara independen dari pemuatan DOM. Teknik pramuat hanya akan bekerja jika kanvas digambar setelah pramuat.

Solusi saya, meskipun itu bukan yang terbaik:

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();
}
Eduardo Mihalache
sumber
-4

Tambahkan penundaan seperti di bawah ini

<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>
Masa bodo
sumber