การวาดข้อความเป็น <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
Thomas

คำตอบ:


74

การวาดภาพบนผืนผ้าใบจะต้องเกิดขึ้นและกลับมาทันทีเมื่อคุณเรียกใช้fillTextเมธอด อย่างไรก็ตามเบราว์เซอร์ยังไม่ได้โหลดแบบอักษรจากเครือข่ายซึ่งเป็นงานเบื้องหลัง ดังนั้นจึงมีการถอยกลับไปยังตัวอักษรมันไม่ได้มี

หากคุณต้องการให้แน่ใจว่าฟอนต์พร้อมใช้งานให้มีองค์ประกอบอื่น ๆ ในหน้าโหลดไว้ล่วงหน้าเช่น

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

3
คุณอาจอยากเพิ่มdisplay: noneแต่นั่นอาจทำให้เบราว์เซอร์ข้ามการโหลดแบบอักษร ควรใช้ช่องว่างแทนไฟล์..
โทมัส

6
การใช้ช่องว่างจะทำให้ IE ทิ้งโหนดช่องว่างที่ควรจะอยู่ใน div ออกไปโดยไม่เหลือข้อความที่จะแสดงในแบบอักษร แน่นอนว่า IE ยังไม่รองรับผืนผ้าใบดังนั้นจึงไม่ทราบว่า IE ในอนาคตจะดำเนินการต่อไปหรือไม่และจะมีผลต่อพฤติกรรมการโหลดแบบอักษรหรือไม่ แต่เป็นปัญหาในการแยกวิเคราะห์ HTML ของ IE ที่มีมายาวนาน
bobince

1
ไม่มีวิธีที่ง่ายกว่าในการโหลดแบบอักษรล่วงหน้าหรือไม่? เช่นบังคับผ่านจาวาสคริปต์อย่างใด?
Joshua

@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
ลองใช้วิธีนี้กับแบบอักษร. tf - ทำงานไม่เสถียรบน Chrome (41.0.2272.101 ม.) แม้แต่ setTimeout ใน 5 วินาทีก็ไม่ช่วย - การเรนเดอร์ครั้งแรกจะใช้แบบอักษรเริ่มต้น
Mikhail Fiadosenka

2
คุณควรตั้งค่า onerror handler ก่อนตั้งค่า src
asdjfiasd

1
"รูปภาพใหม่" ด้วย ไม่มีวงเล็บ
asdjfiasd

@asdjfiasd ไม่จำเป็นต้องใช้ parens และแม้ว่าsrcจะตั้งค่าแอตทริบิวต์ไว้การโหลดจะเริ่มในบล็อกการดำเนินการถัดไป
เนิร์ดแบบจ่ายเงิน

16

จุดสำคัญของปัญหาคือคุณพยายามใช้แบบอักษร แต่เบราว์เซอร์ยังไม่ได้โหลดและอาจไม่ได้ร้องขอด้วยซ้ำ สิ่งที่คุณต้องการคือสิ่งที่จะโหลดแบบอักษรและให้คุณโทรกลับเมื่อโหลดแล้ว เมื่อคุณได้รับการติดต่อกลับคุณจะทราบว่าสามารถใช้แบบอักษรได้

ดูของ Google WebFont พับ ; ดูเหมือนว่าผู้ให้บริการ "กำหนดเอง" และการ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คุณกำลังขาดหายไป ดูคำตอบของ bruno
Pacerier

ขอบคุณ @Pacerier! อัปเดตคำตอบของฉัน
Fred Bergman

เทคโนโลยีนี้อยู่ระหว่างการทดลองและเบราว์เซอร์ส่วนใหญ่ไม่รองรับ
Michael Zelensky

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/

แก้ไขได้โดยการเพิ่ม font-family ลงใน canvas โดยตรงใน CSS ดังนั้นคุณจึงสามารถเพิ่ม

ผ้าใบ {font-family: PressStart; }


3
ดูเหมือนจะไม่ทำงาน: ทดสอบใน Chrome 12แก้ไข: รีเฟรชสองสามครั้งเพื่อให้ไม่ได้แบบอักษร
Ryan Badour

1

ฉันไม่แน่ใจว่าสิ่งนี้จะช่วยคุณได้หรือไม่ แต่เพื่อแก้ปัญหาเกี่ยวกับโค้ดของฉันฉันเพียงแค่สร้าง for loop ที่ด้านบนของ 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 อักษรเปค ช่วยให้คุณสามารถลงทะเบียนรีจิสเตอร์การโทรกลับเมื่อโหลดฟอนต์ทั้งหมดแล้ว คุณสามารถชะลอการวาดผืนผ้าใบของคุณได้ (หรืออย่างน้อยก็วาดข้อความลงในผืนผ้าใบของคุณ) จนกว่าจะถึงเวลานั้นและเริ่มการวาดใหม่เมื่อแบบอักษรพร้อมใช้งาน


1

บทความนี้จัดการปัญหาของฉันเกี่ยวกับแบบอักษรที่โหลดแบบขี้เกียจไม่ปรากฏขึ้น

วิธีโหลดแบบอักษรของเว็บเพื่อหลีกเลี่ยงปัญหาด้านประสิทธิภาพและเพิ่มความเร็วในการโหลดหน้าเว็บ

สิ่งนี้ช่วยฉัน ...

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

0

ก่อนอื่นให้ใช้ Web Font loader ของ Google ตามคำแนะนำในคำตอบอื่น ๆ และเพิ่มรหัสรูปวาดของคุณในการเรียกกลับที่ระบุเพื่อระบุว่ามีการโหลดแบบอักษรแล้ว อย่างไรก็ตามนี่ไม่ใช่จุดจบของเรื่อง จากจุดนี้มันขึ้นอยู่กับเบราว์เซอร์มาก ส่วนใหญ่แล้วจะใช้งานได้ดี แต่บางครั้งอาจต้องรอสองสามร้อยมิลลิวินาทีหรือใช้ฟอนต์ที่อื่นในหน้านี้ ฉันลองใช้ตัวเลือกต่างๆและวิธีการหนึ่งที่ afaik ใช้ได้ผลคือวาดข้อความทดสอบในผืนผ้าใบอย่างรวดเร็วด้วยชุดแบบอักษรและขนาดตัวอักษรที่คุณจะใช้ คุณสามารถทำได้โดยใช้สีเดียวกับพื้นหลังดังนั้นจึงไม่สามารถมองเห็นได้และจะเกิดขึ้นเร็วมาก หลังจากนั้นแบบอักษรก็ใช้ได้กับฉันและในทุกเบราว์เซอร์


0

คำตอบของฉันตอบแบบอักษร Google Web แทนที่จะเป็น @ font-face ฉันค้นหาวิธีแก้ปัญหาแบบอักษรไม่ปรากฏในแคนวาสทุกที่ ฉันลองใช้ตัวจับเวลา setInterval ไลบรารีการหน่วงเวลาแบบอักษรและกลเม็ดทุกประเภท ไม่มีอะไรทำงาน (รวมถึงการใส่ font-family ใน CSS สำหรับ canvas หรือ ID ขององค์ประกอบ canvas)

อย่างไรก็ตามฉันพบว่าข้อความเคลื่อนไหวที่แสดงในแบบอักษรของ Google ทำงานได้อย่างง่ายดาย อะไรคือความแตกต่าง? ในแอนิเมชั่นแคนวาสเราวาดรายการเคลื่อนไหวซ้ำแล้วซ้ำอีก ฉันจึงมีความคิดที่จะแสดงผลข้อความสองครั้ง

ไม่ได้ผลเช่นกัน - จนกว่าฉันจะเพิ่มการหน่วงเวลาสั้น ๆ (100ms) ฉันได้ทดสอบบน Mac เท่านั้น Chrome ทำงานได้ดีที่ 100ms Safari ต้องการการโหลดหน้าเว็บใหม่ดังนั้นฉันจึงเพิ่มตัวจับเวลาเป็น 1,000 แล้วก็ใช้ได้ 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>
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.