CSSのrel=preloadを使った遅延読み込みによる高速化
CSSを遅延読み込みにすることによって、サイトの表示速度を向上することができます。このカスタマイズをテーマで実装しておくのはよいアイデアでしょう。
ユーザーの体感速度も向上しますし、画像の遅延読み込み同様、Googleの提供する診断ツールPageSpeed Insightsでのスコアも改善します。
CSSを遅延読み込みするメリット
まず、CSSをブラウザがどのように解釈するかを理解しておきましょう。通常、CSSはHTMLのhead
タグ内に書かれます。
<link rel="stylesheet" href= "/path/to/css" />
- ブラウザはHTMLを受け取り、
head
タグ内にCSSを発見するとそのダウンロードを試みます。 - CSSのダウンロードが完了すると、ブラウザはそれをパースし、スタイルコンテキストツリーとして解釈します。
- HTMLのダウンロードが完了すると、DOMツリーとして解釈されます。
- DOMに対してスタイルを適用し、レンダリングします。
この一連の流れの中で、ブラウザはレイアウト計算のために、CSSを読み込んだときにその解釈(CSSOMの構築)が終わるまでレンダリングを保留します。この特徴から、CSSはレンダリングブロックリソースと呼ばれます(参照)。
CSSを遅延読み込みすることで、このレンダリングブロック発生を抑えることができ、結果的にユーザーに対してコンテンツの表示される時間が短くなります。
CSSの遅延読み込み方法
CSSの遅延読み込みは次の方法で行うことができます。
<link rel="preload" href="/path/to/style.css" as="style" onload="this.onload=null;this.rel='stylesheet'" />
ただし、この方法はInternet Explorer 11以下およびFirefoxで対応されていませんので、ポリフィルを導入する必要があります。こうしたポリフィルとしては、loadCSSが有名です。このライブラリはMITライセンスなので、WordPressテーマに同梱することができます。
rel=preload
に対応する- JavaScriptなしでも動作する
- ポリフィルによって未対応ブラウザでも動作する
上記の処理を合わせると次のようになります。
<!-- rel=preload -->
<link rel="preload" href="path/to/mystylesheet.css" as="style" onload="this.rel='stylesheet'">
<!-- No JS users. -->
<noscript><link rel="stylesheet" href="path/to/mystylesheet.css"></noscript>
<!-- Polyfill -->
<script>
/*! loadCSS. [c]2017 Filament Group, Inc. MIT License */
!function(n){"use strict";n.loadCSS||(n.loadCSS=function(){});var o=loadCSS.relpreload={};if(o.support=function(){var e;try{e=n.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),o.bindMediaToggle=function(t){var e=t.media||"all";function a(){t.addEventListener?t.removeEventListener("load",a):t.attachEvent&&t.detachEvent("onload",a),t.setAttribute("onload",null),t.media=e}t.addEventListener?t.addEventListener("load",a):t.attachEvent&&t.attachEvent("onload",a),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(a,3e3)},o.poly=function(){if(!o.support())for(var t=n.document.getElementsByTagName("link"),e=0;e<t.length;e++){var a=t[e];"preload"!==a.rel||"style"!==a.getAttribute("as")||a.getAttribute("data-loadcss")||(a.setAttribute("data-loadcss",!0),o.bindMediaToggle(a))}},!o.support()){o.poly();var t=n.setInterval(o.poly,500);n.addEventListener?n.addEventListener("load",function(){o.poly(),n.clearInterval(t)}):n.attachEvent&&n.attachEvent("onload",function(){o.poly(),n.clearInterval(t)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:n.loadCSS=loadCSS}("undefined"!=typeof global?global:this);</script>
上記のポリフィルスクリプトはloadCSSのバージョン2.1.0から取得できます。
npm install --save-dev fg-loadcss@2.1.0
なお、loadCSSはパフォーマンス向上のためバージョン3から上記のポリフィルとは少し変わった読み込み方(メディア属性の書き換え)を指定するようになりました。この方法でも問題なく動くようです。
<link rel="stylesheet" href="/path/to/my.css" media="print" onload="this.media='all'; this.onload=null;">
media属性をprintにしておくことでレンダリングブロックではないとブラウザに宣言し、読み込みが終わった後にメディア属性を書き換える手法です。
クリティカルCSSを判別する
さて、CSSを遅延読み込みすると画面表示が早くなるという前提はよいのですが、すべてのCSSを遅延読み込みすると、画面が一瞬CSSの適用されていない状態で見えてしまいます。
推奨されるのは、レンダリングにおいてクリティカルなCSSだけ通常の読み込みをし、その他は遅延読み込みをする戦略です。Googleの推奨する項目としては「ファーストビューで利用しているCSS」とのことなのですが、WordPressのように動的なCMSではファーストビューのレンダリング状況を確定するのは難しいと思いますので、テーマのメインCSSだけをクリティカルと考えるとよいでしょう。もちろん、さらなるパフォーマンス向上のために、テーマのCSSをさらに細分化することはユーザー体験を向上させるために役立つでしょう。
まとめ
それでは最後に実際にWordPressテーマで利用しているコードを紹介しましょう。
読み込みタグの変更
CSSを読み込むlink
要素をフィルターフックで変更します。
/**
* Override style tag for rel="preload"
*
* @param string $tag HTML link tag.
* @param string $handel Style handle name.
* @param string $href URL to css.
* @param string $media Media.
*/
add_filter( 'style_loader_tag', function( $tag, $handle, $href, $media ) {
// If in admin or already in footer, skip.
if ( is_admin() || did_action( 'wp_footer' ) ) {
return $tag;
}
// If it's critical CSS, skip.
if ( in_array( $handle, [ 'my-theme-style' ] ) ) {
return $tag;
}
// Change default link element to rel=preload and wrap with noscript.
$html = <<<'HTML'
<link rel="preload" href="%1$s" as="style" onload="this.onload=null;this.rel='stylesheet'" data-handle="%3$s" media="%4$s" />
<noscript>
%2$s
</noscript>
HTML;
return sprintf( $html, $href, $tag, $handle, $media );
}, 10, 4 );
ポリフィルの読み込み
続いて、テーマディレクトリの /dist/js/cssrelpreload.min.js
として保存してあるポリフィル用のJavasScrriptをhead
タグの後方に書き出します。
add_action( 'wp_head', function() {
printf( "<script>\n%s\n</script>", file_get_contents( get_template_directory() . '/dist/js/cssrelpreload.min.js' ) );
}, 100 );
これでCSSの遅延読み込みが実現できました。
その他考慮すべきこと
- この機能は単体のプラグインとして実現しても面白いかもしれません。
- ユーザーによってはプラグインの組み合わせなどでうまく動かなくなるかもしれません。そのために遅延読み込みをオフにできる設定を追加するとよいでしょう。