CSS Font LoadingとFont Face Observer、Web Font Loader

ウェブ標準であるCSS Font Loadingが気軽に使えるようになるにはまだまだ時間がかかりそうだ。そのポリフィルであるFont Loaderは動作が不安定かつ開発が止まっており信用できない。代替技術としてはポリフィルと同じ開発者が積極的にコミットしているFont Face Observerと、広く使われているWeb Font Loaderがある。同じフォントの読み込みを検知する場合、この三者ではどのようにコードが変わってくるのだろうか。

以下のコード例では、自前でホスティングしているOpen Sansの読み込みが完了・失敗したらbody要素にクラスを振るという単純なケースを書き分ける。

CSS Font Loading

CSS Font Loading仕様はPromiseによる実装で、読み込み待ちはPromiseで解決することになる。

new FontFace(
  'Open Sans',
  'url(/assets/font/open-sans/r.woff)'
).load().then(function (fontface) {
  document.fonts.add(fontface);
  document.body.className += ' loaded';
}, function (reason) {
  document.body.className += ' failed';
  throw new Error(reason);
});

気をつけるのはウェブ・フォントのURLをurl()でくくる必要があること、そして読み込み成功時にadd()することくらいだろう。Promiseなので、読み込み失敗はthen()の第二引数に指定した関数で処理すれば良い。

Font Face Observer

Font Face Observerはウェブ標準と同じくPromiseによる実装のためウェブ標準と置換えがしやすい……と思いきや、ウェブ・フォントを読み込む機能自体は持っていない。そのためウェブ・フォントCSSを自前で読み込み、そのウェブ・フォントが利用可能になったかどうかをもって読み込み完了が決定される。

var cssFont = document.createElement('link');
cssFont.href = '/assets/css/open-sans.css';
cssFont.rel = 'stylesheet';
document.head.appendChild(cssFont);
new FontFaceObserver(
  'Open Sans',
  {}
).check().then(function () {
  document.body.className += ' loaded';
}, function (reason) {
  document.body.className += ' failed';
  throw new Error(reason);
});

気をつけるのはFontFaceObserverコンストラクターへ渡す第一引数の値と読み込むウェブ・フォントCSSでのfont-familyの値が一致していることだ。こちらもPromiseのため第二引数に指定した関数で読み込み失敗を処理できる。ただし読み込みする機能自体を持っておらずイベント監視(スクロールイベントで監視しているので軽量)のため、デフォルトの3秒、またはcheck()の第二引数で指定したミリ秒数のタイムアウトでも読み込み失敗になる。

Web Font Loader

使っている人はおなじみだろうがWeb Font LoaderはPromiseによる実装ではない。ウェブ標準とはコードが大きく異る。

WebFont.load({
  custom: {
    families: ['Open Sans'],
    urls: ['/assets/css/open-sans.css']
  },
  fontactive: function () {
    document.body.className += ' loaded';
  },
  fontinactive: function (family) {
    document.body.className += ' failed';
    throw new Error('Web font "' + family + '" not loaded.');
  }
});

このようにライブラリーが用意するWebFontオブジェクトのload()メソッドですべて指定することになる。自前で用意したウェブ・フォントを使いたい場合はcustomフィールドでfamiliesurlsを指定することで読み込みを行う。Font Face Observerとは違うウェブ・フォントの読み込み機能自体は半分(ウェブ・フォントCSSの読み込みだけ)内蔵されているということだ。読み込みエラーはfontinactiveフィールドで指定した関数で処理できる。こちらもタイムアウト(デフォルトでは5秒)でもエラーになる。


コード量の面でもPromiseで書くことができるという面でもウェブ標準での実装が優秀だが、ほぼChrome以外で使えないないものは使えない上、ポリフィルは本当に信用ができない。Font Face ObserverはPromiseによる実装で良さそうに見えるが、少し前のリリース(v1.4.1)では何度も呼ぶとかなりの確率で読み込みに成功しても(フォント・ファイル自体は正常に読み込まれている)失敗の方の関数が呼ばれることが高確率で起こっていた。

結局のところ、まだまだWeb Font Loaderで安定ということになると思う。Font Face Observerは悪くはないけれども、ポリフィルでないのなら安定のWeb Font Loaderの方がマシだろう。軽量という点はすこし魅力的だが、多くの場合はウェブページごとに一回呼ぶだけだろうと思われるので、それほど魅力的なファクターではない。

僕は今のところはWeb Font Loaderを使いつつ、常にウェブ標準での置換えを意識しておくという書き方をしている。Font Face Observerは使う利点を見出すことができなかった。