ブログ

pagespeedinsightsで『レンダリングを妨げるリソースの除外』する #1
JavaScript(JS)を遅延ロードする

カトウ
PageSpeed Insightsで指摘される『レンダリングを妨げるリソースの除外』を解消する方法を紹介します。
 
『レンダリング』とは『≒画像なども含めてページを表示する』ことをいいます。
 
ブラウザはレンダリングのために、HTMLを解析しDOMツリー(※1)を構築していきますが、その途中に<script>が見つかると、解析を中断し<script>(※2)を実行します。
 
なので、何も対策がされていない<script>の数だけHTMLの解析が中断され、レンダリングまでの時間がどんどん遅くなってしまいます。
 
※1 このとき構築したDOMツリーは、開発者ツールのElementsタブから確認することができます。
※2 src属性が付いている場合は、ファイルを読み込みます。
 
今回は、『レンダリングを妨げるリソースの除外』の中で<script>が原因になっている部分を解消していきます。
 

デフォルト(同期読み込み+都度実行)

<script type="text/javascript" src="/js/example.js"></script>
この状態では、先ほどの説明どおり、HTMLの解析を中断し/js/example.jsを読み込み、中に書かれているJavaScriptが実行されます。
 

async属性(非同期読み込み+都度実行)

<script type="text/javascript" src="/js/example.js" async></script>
async属性を付けることで、非同期読み込みに変わり、HTMLの解析を中断しなくなります。
 
ただし、読み込みが完了すると実行してしまうので、そのタイミングでHTMLの解析が完了していないと、解析が中断されてしまいます。
 
また、複数のファイルを読み込んでいる場合、それぞれのタイミングで実行するため、<script>を記述(出現)した順番と実行される順番はバラバラになります。
 
なので、ライブラリなどの実行の順番が重要になるものは、下記のdefer属性を使います。
 
/js/example-1.js
console.log('example-1.jsを実行しました');
/js/example-2.js
console.log('example-2.jsを実行しました');
index.html
<script type="text/javascript" src="/js/example-1.js" async></script>
<script type="text/javascript" src="/js/example-2.js" async></script>
console
example-1.jsを実行しました
example-2.jsを実行しました
or
example-2.jsを実行しました
example-1.jsを実行しました
 

defer属性(非同期読み込み+遅延実行)

<script type="text/javascript" src="/js/example.js" defer></script>
defer属性を付けることで、読み込みが完了しても、HTMLの解析が完了するまで実行しないので、解析が中断されることは無くなります。
 
実行の順番は<script>を記述(出現)した順番と同じになります。
 
/js/example-1.js
console.log('example-1.jsを実行しました');
/js/example-2.js
console.log('example-2.jsを実行しました');
index.html
<script type="text/javascript" src="/js/example-1.js" defer></script>
<script type="text/javascript" src="/js/example-2.js" defer></script>
console
example-1.jsを実行しました
example-2.jsを実行しました
 
それぞれのパターンで、読み込みと実行を図にすると以下のようになります。
 
非同期読み込みをすることで短縮されているのがイメージできたと思います。
 

その他(遅延読み込み+都度実行)

非同期読み込みとは違いますが、ユーザがスクロールやキー操作など、アクションを起こすまで、読み込まない方法です。
 
<script>
(function(window,document){
	// <script>を追加する関数です
	// 第二引数に読み込み完了後のコールバックを設定できます
	function insertScript(src,callback=function(){}){
		var addScript=document.createElement('script');
		addScript.type='text/javascript';
		addScript.async=true;
		addScript.src=src;
		addScript.onload=callback;
		var sc=document.getElementsByTagName('script')[0];
		sc.parentNode.insertBefore(addScript,sc);
	}

	// 読み込みたいscriptを指定します
	function main(){
		// async属性の時と同じ実行の順番はバラバラになります
		insertScript('/js/example-1.js');
		insertScript('/js/example-2.js');

		// コールバックに次の insertScript() を入れることで、defer属性のように実行の順番を指定できます
		insertScript('/js/example-1.js',function(){
			insertScript('/js/example-2.js');
		});

		// jQueryプラグインなどの実行もコールバックに入ります
		insertScript('/js/jquery.js',function(){
			insertScript('/js/jquery.myplugin.js',function(){
				$('.myplugin').myplugin([options]);
			});
		});
	}

	// 連続してイベントが発生しても1回しか処理しないようにします
	var lazyLoad=false;
	function onLazyLoad(){
		if(lazyLoad===false){
			lazyLoad=true;
			window.removeEventListener('scroll',onLazyLoad);
			window.removeEventListener('mousemove',onLazyLoad);
			window.removeEventListener('mousedown',onLazyLoad);
			window.removeEventListener('touchstart',onLazyLoad);
			window.removeEventListener('keydown',onLazyLoad);
			main();
		}
	}

	// スクロールイベントやマウス操作など、ユーザの操作を監視します
	window.addEventListener('scroll',onLazyLoad);
	window.addEventListener('mousemove',onLazyLoad);
	window.addEventListener('mousedown',onLazyLoad);
	window.addEventListener('touchstart',onLazyLoad);
	window.addEventListener('keydown',onLazyLoad);
	window.addEventListener('load',function(){
		// 再読み込みなど、既にページがスクロールしている場合
		if(window.pageYOffset){
			onLazyLoad();
		}
	});

	// 外部のJSから insertScript() を読み込みたいとき
	window.lazyScriptLib=window.lazyScriptLib || {};
	window.lazyScriptLib.insertScript=insertScript;
})(window,document);
</script>