完全に状況を掌握した CSS の遅延読み込みの実現
Chrome には <link>
を再利用する場合に、2回目以降のイベントが発生しない問題がありました。(2024/09/19)
Wii の Opera 9.30でテストして Opera の B グレードを 9~9.27 → 9~9.30 に修正しました。(2024/02/12)
現在の実装に沿って動作の記述を変更しています。(2023/07/28)
Gecko が A グレードになるバージョンを特定しました。(2023/07/16)
本記事の調査結果を元に記述した ExternalCSSLoader.js をあわせてご確認ください。
3行まとめ
- A, B, C, Z(動的外部 CSS に非対応) の4つのグレードに分けられる
- A グレード(
<link>
にonload
,onerror
を備える)ブラウザの登場は意外に遅め - C グレードでもあまりパフォーマンスを損なわない
はじめに
本記事のタイトルの「CSS」を「画像」に替えると、皆さんご存知 uupaa 氏による優良記事「完全に状況を掌握した画像の遅延読み込みの実現」と同じになります。
さて、外部 CSS のロード(からスタイルの適用まで)の成否を掌握したい場合に、最新のブラウザでは onload
, onerror
が使えます(A グレード)。
しかし旧いブラウザだと、ブラウザ毎に異なるイベントを使用した上で、外部 CSS に仕込んだテスト用のスタイルで要素のサイズの変化を検出して成功判定するひと手間が必要でした。
更に Gecko 0.9.1未満は、そもそも動的な外部 CSS を非サポートであることが分かりました(Z グレード)。ちょっと寂しいですが、今回は Web フォントの読み込みをネットワーク監視ツールなどでブロックされた場合に、Data URI 化して CSS に埋め込んだフォントで迂回するのが目的です。Web フォントに非対応の当該ブラウザでは一旦困りません。Gecko が Web フォントをサポートするのは1.9.1以降になります。
テスト結果
最も省コストで掌握できる onload
, onerror
を備えるブラウザは意外に後になってから登場したことが分かります。
一方で img.onerror
または img.addEventListener('load', ...)
を使ってロード完了(ネットワークエラー含む)を検出するハック(C グレード)を組み合わせれば、調査した全てのブラウザで polyfill 出来ることも分かりました。
異常系は存在しないファイルを指定してネットワークエラーを出しているケースです。
- 隠し要素のサイズを測る等で可否の判定が可能
link.readyState
はundefined
B グレードについて
これらのブラウザは、<link>
の onload
, onreadystatechange
でロード完了を検出できます。続いて隠し要素のサイズを測る等して、成功とネットワークエラーを判定します。
C グレードについて
これらのブラウザは、<link>
の href
に加えて Image
インスタンスの src
に CSS を指定します。続いて img.onerror
コールバック内で1秒のタイマーをセットします。(2023/07/28 現在の実装に沿って修正)
Opera 7.2x だけは img.addEventListener('load', ...)
を使いました。どんなコードになろうと実現できればオール OK です。
成功の場合、既に CSS はロードされているので、一瞬で CSS をパースしてスタイル適用まで進むはずです。タイマーで繰り返し要素のサイズをチェックします。1秒が経過したらネットワークエラーだと判定します。(2023/07/28 現在の実装に沿って修正)
Chrome 2で img.onerror
がコールバックされないケースがあっため、5秒のタイマーによるバックアップを追加しました。(2023/07/16 追記)
因みに、タイマーを使わずに onscroll
を使う実装を Web フォントのロード監視ライブラリ fontfaceobserver の /src/ruler.js ではしています。この為に複数の隠し要素が必要ですが、タイマーよりイベントを使った方がエレガントですね。テスト用のページを用意しています。
未検証の情報
Google Code Prettify には link.error
というプロパティが居るのですが、これをコールバックするブラウザには遭遇していません。
if (i + 1 < n) { // http://pieisgood.org/test/script-link-events/ indicates that many // versions of IE do not support onerror on <link>s, though // http://msdn.microsoft.com/en-us/library/ie/ms535848(v=vs.85).aspx // indicates that recent IEs do support error. link.error = link.onerror = function () { load(i + 1); }; }
参考リンク
- Browser CSS/JS loading capabilities
img.onerror
を使うアイデアの元ネタ「img-JSONP」について