console.blog(self);

技術、読んだ本、いろいろ。

CSS Font Loading Module Level 3を試してみた

1年以上前から、ちゃんと調べたいと思っていたけど、やっていなかった。

CSS Font Loading Module Level 3は、2014/06/30時点で最終草案(Last Call Working Draft) になっている。CSS Font Loading Module Level 3はフォントのローディングに関する仕様書

最新のEditor's Draftはこっち。

フォントについての仕様書CSS Font Module Level 3で、2014/06/30時点で勧告候補(Candidate Recommendation)になっている。

仕様書読んで、サンプル書いてみた。サンプルの内容については後述する。

FOUT(Flash of Unstyled Text)

CSS Font Load Moduleがなぜ必要なのかというと、FOUT(Flash of Unstyled Text)という問題があるから。

WebFontsを利用するとき、スタイルの適用されていないテキストが一瞬表示されてしまう、という問題。フォントが読み込まれるまでの間、デフォルトのフォントでテキストが表示される。

WebFontsでは、そのフォントを使用する部分が現れたときに、フォントをロードする。フォントのロードのせいで、全体の読み込みが遅くなったら困るよね。だから遅延読み込み(lazy load)されるようになっている。

The @font-face rule is designed to allow lazy loading of font resources that are only downloaded when used within a document.

4.8 Font loading guidelines - CSS Fonts Module Level 3

だから、FOUTが発生する。一瞬フォントが適用されないくらいならまだいいけど(よくないけど)、CanvasとかでWebFontsを使おうとすると、Lazy Loadだとフォントが適用されなくなってしまう。Canvasの描画後に、フォントをダウンロードするけど、もう描画されているからフォントが適用されない。

この問題に、以前WebFontsのLTやったときにはまった。

このときは、先にHTMLでWebFontsを無駄に使って、フォントが先にロードされるようにした。涙ぐましい努力の痕。

<span style="font-family: 'Monofett';">&nbsp;</span>
<span style="font-family: 'Cabin Sketch';">&nbsp;</span>

Web Font Loaderを使うと、もっとちゃんと対応できる。

と、これまでのWebFontsのローディングは、ちょっとめんどうだった。

CSS Fonts Module Level 3の策定

WebFontsのローディングに対して、CSS Fonts Module Level 3 Working Draft 11 December 2012では、新しくFontLoaderというインターフェースが定義された。onloadとかonerrorといったイベントハンドラが定義され、コールバックであれこれできるようにしよう、って感じ。

CSS Fonts Module Level 3 Last Call Working Draft 11 July 2013では、フォントのローディングに関する仕様が、別の仕様に切り出されていた。

The description of font load events was moved into the CSS3 Font Load Events module.

この切り出された仕様が、CSS Font Loading Module Level 3になって、いまに至る。

最初はコールバックを使う仕様だったけど、いまはPromiseを使う仕様になっている。

This specification uses Promises, which are defined in ECMAScript 6.

2014/05のCSS Working GroupのFace to Face meetingでは、こんな感じだったみたい。

今回のミーティングではAPIが依存するECMAScript 6のPromisesの状況がどうなっているかといった軽い確認のみされ、Last Callとすることが決まりました。F2Fの終了翌日の22日に、Last Call Working Draftが公開されています。

関係ないけど、promise本、読んでないから読まなくては。

ブラウザの実装状況

PromiseとCSS Fonts Moduleの実装状況はこんな感じ。

Promise

Promiseは、Chrome 33 / Firefox 30から使えるようになっている。

Chrome 37では chrome://flags で “Enable experimental Web Platform features” を有効にしなくても、Promiseが利用できた。いつからだろう。

CSS Fonts Module

実装されていれば、 document.fonts があるみたい。

Constructing a new FontFace object and adding it to document.fonts:

Chrome 37では実装されていて、フラグなしで利用できた。Chrome 35で実装されたみたい。

CSS Font Loading can be used to dynamically load font resources. It gives developers more control over the user experience on pages that use Web Fonts. For instance, you can ask Chrome to start downloading the web fonts you'll need on demand and be notified when they become available.

Firefox(Firefox Nigthly 33.0a1 2014-06-28)ではまだ使えなかったけど、こんなissueがあって実装も進んでるみたいだから、もうすぐ使えるようになるかな。

polyfillもあるね。

CSS Fonts Module Level 3を読みながら、試してみる

仕様書を読みながら、サンプルを作って試してみた。WebFontsにはGoogle Fontsを使った。

CSS Fonts Module Level 3について、すでにすてきな記事があって、参考にさせていただきました。

ちなみにGoogle FontsのCSSは、ブラウザ毎に異なった内容を返してくれる。利用しているフォントの種類が違う。こんな風に読み込んで

 <link href='http://fonts.googleapis.com/css?family=Lobster' rel='stylesheet' type='text/css'>

Chromeだとこんな内容に

@font-face {
  font-family: 'Lobster';
  font-style: normal;
  font-weight: 400;
  src: local('Lobster'), url(http://fonts.gstatic.com/s/lobster/v9/G6-OYdAAwU5fSlE7MlBvhQLUuEpTyoUstqEm5AMlJo4.woff2) format('woff2'), url(http://fonts.gstatic.com/s/lobster/v9/NIaFDq6p6eLpSvtV2DTNDQLUuEpTyoUstqEm5AMlJo4.woff) format('woff');
}

IEだとこんな内容になる。

@font-face {
  font-family: 'Lobster';
  font-style: normal;
  font-weight: 400;
  src: url(http://fonts.gstatic.com/s/lobster/v9/RbZDm27knEncUX6lPsjRBg.eot);
  src: local('Lobster'), url(http://fonts.gstatic.com/s/lobster/v9/RbZDm27knEncUX6lPsjRBg.eot) format('embedded-opentype'), url(http://fonts.gstatic.com/s/lobster/v9/MWVf-Rwh4GLQVBEwbyI61Q.woff) format('woff');
}

CSS Fonts Module Level 3では、FontFaceと、FontFaceSetが定義されている。

FontFaceは単一のFontFaceを表す。FontFaceSetはドキュメント全体のFontFaceの状態を表すっぽい。たぶん。

loadメソッドはどちらのインタフェースにもあるので、仕様書読んでてちょっと混乱した。

The FontFace Interface

単一のFontFace。

FontFaceでloadしたオブジェクトはこんな感じだった。

FontFace {
  family: "Lobster",
  featureSettings: "normal",
  loaded: Promise,
  status: "loaded",
  stretch: "normal",
  style: "normal",
  unicodeRange: "U+0-10FFFF",
  variant: "normal",
  weight: "normal",
  __proto__: FontFace
}

The load() method

FontFaceのloadメソッド。Promiseが返却される。

作ったサンプル。

CSSでfont-faceが指定されていない場合、こんな感じでフォントをロードして適用できる。

var f = new FontFace(
  "Lobster",
  "url(http://fonts.gstatic.com/s/lobster/v9/NIaFDq6p6eLpSvtV2DTNDQLUuEpTyoUstqEm5AMlJo4.woff)",
  {}
 );

f.load().then(function (loadedFace) {
  document.fonts.add(loadedFace);
  document.querySelector(".lorem").style.fontFamily = "Lobster, serif";
});

FontFaceコンストラクタの第3引数には、FontFaceDescriptorsが指定できて、styleとかweightをオブジェクトとして渡せる。

かんたん。いい感じ。

The FontFaceSet Interface

document.fontsはFontFaceSet。

フォントが読み込まれたあとのFontFaceSetはこんな感じだった。

FontFaceSet {
  onloading: null,
  onloadingdone: null,
  onloadingerror: null,
  size: 2,
  status: "loaded",
  __proto__: FontFaceSet
}

Events

FontFaceSetのイベント。

作ったサンプル。

CSSFontFaceLoadEventで定義されている。定義されているイベントはこんな感じ。

Event handler Event handler event type
onloading loading
onloadingdone loadingdone
onloadingerror loadingerror

こんな感じでWebFontsを読み込んで

<link href='http://fonts.googleapis.com/css?family=Lobster' rel='stylesheet' type='text/css'>
<style>
  .lorem {
    font-family: 'Lobster', cursive;
  }
</style>

document.fonts.addEventListenerでイベントを登録するみたい。

document.fonts.addEventListener('loading', function() {
  console.log("loading");
});

document.fonts.addEventListener('loadingdone', function() {
  console.log("loadingdone");
});

document.fonts.addEventListener('loadingerror', function() {
  console.log("loadingerror");
});

それぞれのイベントが発火して、logが出力される。

The load() method

FontFaceSetのloadメソッド。Promiseが返却される。

作ったサンプル。

CSSでfont-faceを指定する。

<link href='http://fonts.googleapis.com/css?family=Lobster' rel='stylesheet' type='text/css'>

こんな感じでフォントをロードして適用できる。JavaScriptはこんな感じ。

document.fonts.load("1em Lobster").then(function (loadedFace) {
  document.querySelector(".lorem").style.fontFamily = "Lobster, serif";
});

これもかんたん。いい感じ。FOUT対策は、こっちを使うことが多そう。

The check() method

FontFaceSetのcheckメソッド

作ったサンプル。

特定のフォントが読み込まれたか確認できる。

console.log(document.fonts.check("2em Lobster")); // => false

document.fonts.ready().then(function(fontFaceSet) {
  console.log(fontFaceSet.check("2em Lobster")); // => true
});

環境によるけど、最初にfalseが出力されると思う。ready().then()は準備ができたあとに実行されるので、trueが出力される。

The ready() method

FontFaceSetのreadyメソッド

作ったサンプル。

フォントが利用可能になると ready().then() が実行される。Promiseだと、説明する日本語が難しい…。

こんな感じでWebFontsを読み込んで

  <link href='http://fonts.googleapis.com/css?family=Lobster' rel='stylesheet' type='text/css'>
  <style>
    .lorem {
      font-family: 'Lobster', cursive;
      font-size: 2em;
    }
  </style>

こんな感じで準備ができたあとに、処理できる。

document.fonts.ready().then(function(fontFaceSet) {
  console.log("document.fonts.ready()")
});

Find the matching font faces

FontFaceSet#load() や FontFaceSet#check() の引数に"Lobster"とか渡してもだめで、"1em Lobster"って感じで渡す必要がある。インターフェースはこんな感じになってる。

Promise<sequence<FontFace>> load(DOMString font, optional DOMString text = " ");
boolean check(DOMString font, optional DOMString text = " ");

DOMString fontってなんだろうと思うと、こんなことが書いてある

Find the matching font faces from font face set using the font and text arguments passed to the function, and let font face list be the return value. If a syntax error was returned, throw a SyntaxError exception and terminate these steps.

Find the matching font faces にはこんな感じのことが書いてある。

Parse font using the CSS value syntax of the font property. If a syntax error occurs, return a syntax error.

fontプロパティと同じように指定しなくてはいけない。Canvasのfontプロパティも同じだった。

Canvasと組み合わせたサンプル

こんな感じでうまくいく。

Canvas & WebFonts

var drawText = function(){
  var ctx = document.querySelector("#canvas-area").getContext("2d");
  ctx.fillStyle    = "pink";
  ctx.font         = "72px Monofett, sans-serif";
  ctx.fillText("I love WebFonts", 10 , 100);
};
document.fonts.load("72px Monofett, sans-serif").then(drawText);

window.onload = drawText;だと、こんな感じになっちゃう。

Canvas & WebFonts(not work)

document.fonts.load("72px Monofett, sans-serif").then(drawText);が大切。簡単でいいね。

まとめ

もうちょっとさらっと書きたかったけど、だいぶ長くなっちゃった。

Chrome+HTML5 ConferenceでLTやったのも、WebFontsの記事書いたのも、3年前。記事書いたときのChromeは12だったみたい。いまは37。すごいなぁ。

以下のサンプルはGoogle Web Fontsで提供されるWebFontsを利用しています。サンプルは IE 9/Firefox 6/Chrome 12/Opera 11.5/Safari 5.1 で動作確認済みです。

Chrome 33からリガチャが表示できるみたい。

WOFF 2.0 First Public Working Draftが5月にでた。

CSS Font Loading Module Level 3は5月にLast Call Working Draftになった。

ちょこちょこ動きがあって楽しいね。

ところで、こんなにがっつりW3Cの英語の仕様書を読むのは初めてだった。これまは日本語訳を読んだり、気になるところをかいつまんで読んだりする感じだった。

CSS Font Loading Module Level 3は、前から多少読んでいたし、なにより短いので読みやすかった。CSS Fonts Module Level 3はちょっと大きすぎる…。

多少は慣れた気がするので、好きな仕様を読んでいきたい。ちょっとずつでも。

英語が苦手な僕が読んで書いた文章なので、間違っているところもあるかもしれない。なにかあれば、ご指摘いただけるとうれしいです。

Enjoy WebFonts!!