Vẽ văn bản tới <canvas> bằng @ font-face không hoạt động ở lần đầu tiên


93

Khi tôi vẽ văn bản trong canvas với kiểu chữ được tải qua @ font-face, văn bản không hiển thị chính xác. Nó hoàn toàn không hiển thị (trong Chrome 13 và Firefox 5) hoặc kiểu chữ bị sai (Opera 11). Loại hành vi không mong muốn này chỉ xảy ra ở bản vẽ đầu tiên với kiểu chữ. Sau đó mọi thứ hoạt động tốt.

Nó là hành vi tiêu chuẩn hay một cái gì đó?

Cảm ơn bạn.

PS: Sau đây là mã nguồn của trường hợp thử nghiệm

<!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
Trình duyệt tải phông chữ trong nền, không đồng bộ. Đây là hành vi bình thường. Xem thêm paulirish.com/2009/fighting-the-font-face-fout
Thomas

Câu trả lời:


71

Việc vẽ trên canvas phải diễn ra và trở lại ngay lập tức khi bạn gọi fillTextphương thức. Tuy nhiên, trình duyệt vẫn chưa tải phông chữ từ mạng, đây là một tác vụ nền. Vì vậy, nó đã rơi trở lại phông chữ nó không có sẵn.

Nếu bạn muốn đảm bảo rằng phông chữ có sẵn, hãy tải trước một số phần tử khác trên trang, ví dụ:

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

3
Bạn có thể muốn thêm display: none, nhưng điều đó có thể khiến trình duyệt bỏ qua việc tải phông chữ. Tốt hơn là sử dụng một khoảng trắng thay vì a ..
Thomas

6
Việc sử dụng khoảng trắng sẽ khiến IE loại bỏ nút khoảng trắng cần có trong div, không để lại văn bản nào để hiển thị trong phông chữ. Tất nhiên IE chưa hỗ trợ canvas, vì vậy vẫn chưa biết liệu IE trong tương lai có tiếp tục làm điều này hay không và liệu điều đó có ảnh hưởng đến hành vi tải phông chữ hay không, nhưng đó là một vấn đề phân tích cú pháp HTML từ lâu của IE.
bobince

1
Không có cách nào dễ dàng hơn để tải trước phông chữ? Ví dụ: buộc nó thông qua javascript bằng cách nào đó?
Joshua

@Joshua: chỉ bằng cách tạo một phần tử trên trang và đặt phông chữ trên đó, tức là. tạo nội dung tương tự như trên nhưng động.
bobince

17
Việc thêm điều này không đảm bảo rằng phông chữ sẽ được tải khi JavaScript được thực thi. Tôi đã phải thực thi tập lệnh của mình bằng cách sử dụng phông chữ được đề cập bị trì hoãn (setTimeout), tất nhiên, điều này rất tệ.
Nick

23

Sử dụng thủ thuật này và liên kết một onerrorsự kiện với một Imagephần tử.

Demo tại đây : hoạt động trên Chrome mới nhất.

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
thủ thuật thông minh. lưu ý rằng bạn đang tải css, đến lượt nó, chứa một tham chiếu đến tệp phông chữ thực (ví dụ: .ttf, .woff, v.v.). Tôi đã phải sử dụng thủ thuật của bạn hai lần, một lần cho tệp css và một lần cho tệp phông chữ được tham chiếu (.woff) để đảm bảo rằng mọi thứ đã được tải.
magma

1
Đã thử phương pháp này với phông chữ .ttf - không hoạt động ổn định trên Chrome (41.0.2272.101 m). Ngay cả setTimeout trong 5 giây cũng không giúp ích gì - kết xuất đầu tiên đi với phông chữ mặc định.
Mikhail Fiadosenka

2
Bạn nên đặt onerror xử lý trước khi bạn thiết lập src
asdjfiasd

1
cũng là "Hình ảnh mới;" thiếu dấu ngoặc đơn.
asdjfiasd

@asdjfiasd Các parens không bắt buộc và mặc dù srcthuộc tính đã được đặt, quá trình tải sẽ bắt đầu trong khối thực thi tiếp theo.
một mọt sách được trả tiền vào

16

Điểm mấu chốt của vấn đề là bạn đang cố gắng sử dụng phông chữ nhưng trình duyệt vẫn chưa tải nó và thậm chí có thể chưa yêu cầu nó. Những gì bạn cần là một thứ gì đó sẽ tải phông chữ và cung cấp cho bạn một cuộc gọi lại khi nó được tải; khi bạn nhận được cuộc gọi lại, bạn biết bạn có thể sử dụng phông chữ đó.

Nhìn vào Trình tải WebFont của Google ; nó có vẻ giống như một nhà cung cấp "tùy chỉnh" và một activecuộc gọi lại sau khi tải sẽ làm cho nó hoạt động.

Tôi chưa bao giờ sử dụng nó trước đây, nhưng từ việc quét nhanh các tài liệu, bạn cần tạo một tệp css fonts/pressstart2p.css, như sau:

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

Sau đó thêm JS sau:

  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

Bạn có thể tải phông chữ bằng FontFace API trước khi sử dụng nó trong canvas:

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

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

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

Tài nguyên phông chữ myfont.woff2được tải xuống lần đầu tiên. Sau khi quá trình tải xuống hoàn tất, phông chữ được thêm vào FontFaceSet của tài liệu .

Đặc tả của FontFace API là một bản nháp đang hoạt động tại thời điểm viết bài này. Xem bảng tương thích của trình duyệt tại đây.


1
Bạn đang mất tích document.fonts.add. Xem câu trả lời của bruno.
Pacerier

Cảm ơn @Pacerier! Đã cập nhật câu trả lời của tôi.
Fred Bergman

Công nghệ này là thử nghiệm và không được hỗ trợ bởi hầu hết các trình duyệt.
Michael Zelensky

11

Còn việc sử dụng CSS đơn giản để ẩn một div bằng cách sử dụng phông chữ như thế này:

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>



1

Tôi không chắc liệu điều này có giúp ích được cho bạn hay không, nhưng để giải quyết vấn đề với mã của mình, tôi chỉ cần tạo một vòng lặp for ở đầu Javascript của tôi chạy qua tất cả các phông chữ mà tôi muốn tải. Sau đó, tôi chạy một chức năng để xóa canvas và tải trước các mục tôi muốn trên canvas. Cho đến nay nó đã hoạt động hoàn hảo. Đó là logic của tôi, tôi đã đăng mã của mình bên dưới:

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

Tôi đã thêm một số mã ở trên để minh họa câu trả lời của mình. Tôi đã gặp sự cố tương tự khi phát triển một trang web khác và điều này đã giải quyết được nó vì trên một máy chủ, nó tải tất cả các phông chữ do đó cho phép chúng hiển thị chính xác trên trang web.
grapien

1

Tôi đã viết một jsfiddle kết hợp hầu hết các bản sửa lỗi được đề xuất ở đây nhưng không giải quyết được sự cố. Tuy nhiên, tôi là một lập trình viên mới vào nghề nên có lẽ đã không viết mã các bản sửa lỗi được đề xuất một cách chính xác:

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

Cách giải quyết quyết cuối cùng tôi đã chấp nhận và trong lần tải đầu tiên, sử dụng hình ảnh của văn bản trong khi cũng định vị văn bản bằng các mặt phông chữ bên ngoài vùng hiển thị canvas. Tất cả các hiển thị tiếp theo của các mặt phông chữ trong vùng hiển thị canvas đều hoạt động không có vấn đề gì. Đây không phải là một cách giải quyết thanh lịch bằng bất kỳ phương tiện nào.

Giải pháp được đưa vào trang web của tôi nhưng nếu có ai cần tôi sẽ cố gắng tạo jsfiddle để chứng minh.


1

Một số trình duyệt hỗ trợ các tải CSS Font đặc điểm kỹ thuật. Nó cho phép bạn đăng ký đăng ký một cuộc gọi lại khi tất cả các phông chữ đã được tải. Bạn có thể trì hoãn việc vẽ canvas của mình (hoặc ít nhất là vẽ văn bản vào canvas của bạn) cho đến lúc đó và kích hoạt vẽ lại khi phông chữ có sẵn.


0

Trước hết, hãy sử dụng trình tải Phông chữ Web của Google như đã được khuyên trong câu trả lời khác và thêm mã bản vẽ của bạn vào lệnh gọi lại mà nó cung cấp để cho biết phông chữ đã được tải. Tuy nhiên, đây không phải là kết thúc của câu chuyện. Từ thời điểm này, nó rất phụ thuộc vào trình duyệt. Hầu hết thời gian nó sẽ hoạt động tốt, nhưng đôi khi có thể phải đợi vài trăm mili giây hoặc sử dụng phông chữ ở một nơi khác trên trang. Tôi đã thử các tùy chọn khác nhau và một phương pháp mà afaik luôn hoạt động là nhanh chóng vẽ một số thông báo thử nghiệm trong canvas với các tổ hợp phông chữ và kích thước phông chữ mà bạn sẽ sử dụng. Bạn có thể làm điều đó với cùng màu với màu nền, vì vậy chúng thậm chí sẽ không thể nhìn thấy được và nó sẽ diễn ra rất nhanh. Sau đó, phông chữ luôn hoạt động cho tôi và trên tất cả các trình duyệt.


0

Câu trả lời của tôi đề cập đến các phông chữ trên Web của Google thay vì @ font-face. Tôi đã tìm kiếm khắp nơi để tìm giải pháp cho vấn đề phông chữ không hiển thị trong canvas. Tôi đã thử các bộ hẹn giờ, setInterval, thư viện font-delay và tất cả các loại thủ thuật. Không có gì hoạt động. (Bao gồm cả việc đưa font-family vào CSS cho canvas hoặc ID của phần tử canvas.)

Tuy nhiên, tôi thấy rằng văn bản hoạt ảnh được hiển thị bằng phông chữ Google hoạt động dễ dàng. Có gì khác biệt? Trong hoạt ảnh canvas, chúng tôi vẽ lại nhiều lần các mục hoạt hình. Vì vậy, tôi có ý tưởng kết xuất văn bản hai lần.

Điều đó cũng không hoạt động - cho đến khi tôi cũng thêm thời gian trễ hẹn giờ ngắn (100ms). Tôi chỉ thử nghiệm trên máy Mac cho đến nay. Chrome hoạt động tốt ở 100 mili giây. Safari yêu cầu tải lại trang, vì vậy tôi đã tăng bộ đếm thời gian lên 1000, và sau đó ổn. Firefox 18.0.2 và 20.0 sẽ không tải bất kỳ thứ gì trên canvas nếu tôi đang sử dụng phông chữ Google (bao gồm cả phiên bản hoạt hình).

Mã đầy đủ: http://www.macloo.com/examples/canvas/canvas10.html

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


0

Đối mặt với cùng một vấn đề. Sau khi đọc "bobince" và những nhận xét khác, tôi sử dụng javascript sau để giải quyết nó:

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

0

Nếu bạn muốn vẽ lại mỗi khi một phông chữ mới được tải (và có thể thay đổi cách hiển thị) thì api tải phông chữ cũng có một sự kiện tuyệt vời cho điều đó. Tôi đã gặp sự cố với Lời hứa trong một môi trường hoàn toàn năng động.

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

Khung được vẽ độc lập với tải DOM. Kỹ thuật tải trước sẽ chỉ hoạt động nếu canvas được vẽ sau khi tải trước.

Giải pháp của tôi, ngay cả khi nó không phải là tốt nhất:

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

Thêm thời gian trễ như bên dưới

<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>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.