Edited at

CSSを非同期ロードする最も簡単な方法

CSS読み込みの<link rel="stylesheet">は同期なので、レンダリングブロックします。

どういうことかというと、CSSファイルの読み込み・パースが終わるまで画面描写が止まってしまいます。

これに対策する方法としてpreloadというものが策定されましたが、対応状況が微妙です。

2019年7月時点でもブラウザシェアが8割しかなく、Firefoxは当面対応するつもりがないようです。

とはいえ残り2割のためにloadCSSを突っ込んだりとか始めると本末転倒感に溢れます。

全ブラウザ対応のためには、なんにしろ結局JavaScriptをこりこり書くしかない状況でした。

が、なんかすっごい簡単な対処法があったので紹介してみます。

以下はScott Jehlによる記事、The Simplest Way to Load CSS Asynchronouslyの日本語訳です。

ちなみにScott Jehlが属するfilament grouploadCSS開発してるところです。


The Simplest Way to Load CSS Asynchronously

ページパフォーマンスを上げるために我々ができる最も影響の大きい変更のひとつは、ページレンダリングをブロックしない方法でCSSをロードすることです。

デフォルトではブラウザは外部CSSを同期的に読み込むため、CSSがダウンロード・パースされている間は全てのページレンダリングが停止し、そのためレンダリングに遅延が発生します。

もちろん、ページのレンダリングを始める前に最低限のCSSは読み込んでおかなければなりません。

初期画面を速やかに表示するためには、初期表示される部分のCSSだけをインライン化するか、もしくはHTTP/2サーバプッシュするとよいでしょう。

全体の量が大きくないサイトではインライン化だけでも十分かもしれません。

しかしCSSが大きい場合(概ね15-20kb程度以上)は、CSSを優先順位で分割することはパフォーマンス向上に役立ちます。

CSSを分割することができたら、優先順位の低いCSSはバックグラウンドでロードするべきでしょう。

すなわち、非同期です。

この記事では、最新のそのやり方を解説することを目指します。

CSSを非同期にロードする方法はいくつか存在します。

しかし、いずれも思ったほど直感的なやり方ではありません。

script要素と異なり、link要素にはasync属性やdefer属性がありません。

従って、もう少し簡単にCSSを非同期ロードするために、我々は何年もloadCSSプロジェクトを維持してきました。

最近になってようやく、ブラウザはCSSの非同期読み込み動作を標準化しました。

従って、ブラウザ間の小さい差異を吸収するloadCSSのようなプロジェクトは、今後は徐々に必要なくなってくることでしょう。


The code

ブラウザが様々なlink属性をどのように処理するかについての知識が蓄積されてきたため、短いHTMLでCSSを非同期にロードする方法を得ることができました。

これが、スタイルシートを非同期的に読み込む最も簡単な方法です。

<link rel="stylesheet" href="/path/to/my.css" media="print" onload="this.media='all'">


Breaking that down…

このHTMLは非常に簡単ですが、やはり直感的ではないので、これが何をしているのかここで解説しましょう。

最初に、このlinkタグはmedia属性がprintに設定されています。

メディアタイプprintは『印刷の際にこのスタイルシートを適用する』、すなわちユーザがページを印刷しようとするアクションを取ったときに適用されるメディアタイプです。

元々はCSSを印刷だけではなく全てのメディアタイプ、特にscreenに対して適用したかったわけですが、現在の環境に合わないメディアタイプをあえて指定することによって、興味深い効果を得ることができます。

すなわち、ブラウザはレンダリングブロックすることなく、非同期にスタイルシートをロードするのです。

これは有用ですが、我々が望む全てではありません。

CSSがロードされた後で、screenに適用されるようにしたいわけです。

そのために、onload属性を使って、ロードが完了したらメディアタイプをallに変更します。


Can’t rel=preload do this too?

preload属性でも同じことはできませんか?

はい、そのとおりです。

ここ最近はrel=stylesheetのかわりにrel=preloadを使用して上記と同じようなことを実現することができます。

その方法を使っても問題はありませんが、ただしpreloadを使うときには考慮しなければならない点がいくつか存在します。

第一に、ブラウザサポート状況が芳しいものではないため、幅広い環境でpreloadを扱うためにはloadCSSなどのpolyfillがまだまだ必要です。

そしてもうひとつ重要なことが、preloadは非常に高い優先度が設定されていて、真っ先に動作することです。

そのため、実際はさほど重要ではないCSSが先にダウンロードされ、他の重要なファイルのダウンロードが後回しになるといった可能性があります。

幸いなことに、preloadをサポートしているブラウザではpreloadし、そうではない場合はprintするように、両者を組み合わせることができます。

<link rel="preload" href="/path/to/my.css" as="style">

<link rel="stylesheet" href="/path/to/my.css" media="print" onload="this.media='all'">

この単純で自然な文法は、polyfillを使うより好ましいでしょう。

そのため、メディアタイプprintによるアプローチは我々のフェイバリットです。


Why not use a faux media attribute?

どうして未定義のメディアタイプを使わないのですか?

何年か我々の記事をフォローしている読者の中には、我々がメディアタイプprintではなくonly xのような属性値を指定したことがあったことを思い出す人がいるかもしれません。

xは未定義のメディアタイプです。

ブラウザは未定義のメディアタイプに遭遇すると、現在はそれらを既存のメディアタイプと同じように扱います。

すなわち、とりあえずファイルをロードします。

ところが、一部のブラウザ開発チームは、表示先が一致しないメディアタイプと、未定義のメディアタイプを区別することを検討し始めています。

今後、未定義のメディアタイプはロードしないようにされる可能性があります。

これは多くの既存のCSSローディングの実装を壊すため、導入される可能性は高くなさそうです。

しかし万一の可能性を考え、printのような有効でありなおかつ一致しないメディアタイプを使ったほうが安全でしょう。


You may not need loadCSS…

loadCSSはもはや必要がない?

JavaScriptからCSSを呼び出す場合など、一部用途にとってはloadCSSは非常に有用であり、我々もまだloadCSSをメンテし続けていくつもりです。

既にloadCSSを使っていたり、rel=preloadのpolyfillを適用しているサイトでは、必ずしもわざわざHTMLを書き換える必要はありません。

内部的には既にこの記事で解説しているのと同じ仕組みを使用しています。

我々はまだまだ、HTMLに対してシンプルなアプローチを探していきます。

そして大抵の場合、シンプルがベストです。


Thanks for reading!

質問があればTwitterにどうぞ。


感想

CSS 非同期などでググれば色々な解決法が出てきますが、全環境対応の方法としてはおそらく最も単純な方法でしょう。

こんなにあっさりした書き方があるとは驚きだ。

なお、本文中で言っている過去記事というのはこれのことだと思われます。

<link rel="stylesheet" href="mystyles.css" media="nope!" onload="this.media='all'">

この書き方でも動作はしますが、本文にあるように今後は動作しなくなる可能性があります。

素直にprintと書くようにしましょう。

そういえばダミーではなく実際にprint用のCSSを設定したい場合はどうすればいいんだろう。

<link rel="stylesheet" href="/path/to/my.css" media="print" onload="this.media='all'">

<link rel="stylesheet" href="/path/to/print.css" media="print">

これでいいのかな?