フォントの読み込み完了を検知する

TypeKitとかがやってるWebフォントの読み込みが終わったらhtml要素にクラス名を振るアレの話。TypeKitがオープンソースでリリースしているWebFont Loaderを使う方法が安全で安定。Googleがホスティングしているのもあるので手軽でもある。でも読み込み完了の検知以外にも機能があってパワフルすぎる気がするので、Adobe Blankを使って自前で書いてみることにした。

Adobe Blankのサイズ削減

Adobe Blankはそのまま使うと30KB以上ある。読み込み完了検知だけなら幅0のグリフが1つあればそれで良いので、まずはサブセット化してサイズを抑えることから。aだけのWOFFなAdobe Blankを作ればちょうど1KBくらいになる。

Download: adobe-blank.woff

@font-face定義の追加

外部リクエストにすると意味が薄れるのでDataURIを使って既存のCSSに混ぜ込む。

@font-face {
  font-family: 'Adobe Blank';
  src: url('data:application/font-woff;base64,d09GRgAB...') format('woff');
}

読み込み完了を検知するためのHTML要素

これをfont-familyに指定した要素では、aしかテキストのない場合に幅が0になる。なので以下の様な要素を作っておくとWebフォント(Source Sans Proを例にする)の読み込みが完了すると幅が増えることになる。つまりこの要素の幅を監視すれば読み込み完了の検知が出来る。

<div style="font-family:'Source Sans Pro', 'Adobe Blank';
  position: absolute;
  top: -100px;">
  a
</div>

JavaScriptで監視

要素の生成から監視までを全てJavaScriptでやると良い。タイマー使わない方法も模索したい所だけどまずは普通のアプローチで。

function detectFontLoading(fontName) {
  var tester = document.createElement('span');
  tester.style.fontFamily = '"' + fontName + '", "Adobe Blank"';
  tester.style.position = 'absolute';
  tester.style.top = '-100px';
  tester.appendChild(document.createTextNode('a'));
  document.body.appendChild(tester);

  var timerId = setInterval(checkWidth, 500);

  function checkWidth() {
    if (tester.offsetWidth > 0) {
      clearInterval(timerId);
      document.documentElement.className += ' ' + fontName.toLowerCase().replace(/\s/g, '_');
      tester.parentNode.removeChild(tester);
    }
  }
}

detectFontLoading("Source Sans Pro");

これでSource Sans Proの読み込みが完了すると、html要素にsource_sans_proというクラス名が振られる。どのブラウザーでも読み込みの完了前から文字は表示され読めるという前提の話になっているので、特に急がずに500ミリ秒間隔とかでチェックすれば良さそう。もっとゆっくりでも良いかも。ずっとチェックし続けるのでタイムアウトは設定できるようにするべきだけどやってない。

CSSでのフォントの切り替え

あとはCSSをこれに見合った形で書いてやるだけ。

body {
  font-family: "Helvetica Neue", "Calibri", sans-serif;
}

.source_sans_pro body {
  font-family: "Source Sans Pro", sans-serif;
}

Source Sans Proの読み込みが完了するまではHelvetica NeueまたはCalibriでとりあえず表示され、読み込みが完了するとSource Sans Pronに置き換わる。


Webフォントの読み込み完了の検知はつまるところFOUT対策なわけだけど、今後どうなるんだろう。Firefoxの挙動が一番素直でマシだと感じているので、それに揃うといいなぁと思っている。