ぼやき

0

PageSpeed Insights で「修正が必要」と指摘された「画像を最適化する」と「スクロールせずに見えるコンテンツのレンダリングをブロックしている JavaScript/CSS を排除する」ですが、後者は前回対応したので、今回は「画像を最適化する」の対処をしていきたいと思います。

WordPressで「スクロールせずに見えるコンテンツのレンダリングをブロックしているJavaScript/CSSを排除する」に対応する

施策前の PageSpeed Insights のスコアはモバイルが 70 、パソコンが 74 です。

20170518_1.png

なぜ画像を遅延読み込みさせるのか

PageSpeed Insights でテストをしているページ(ダイソン V6 Mattress+ の吸引力は嘘じゃない!)には画像が 25 枚もあります。このサイトの画像はほとんどが Google Photos に置いてあり、自動にリサイズしたものを表示しているので、圧縮などをして最適化することができません。なので邪道ですが、ここは Lazy Load を使いファーストビューに読み込む画像を少なくして、 Google からの修正の指摘を回避しようと思います。

Lazy Load は日本語では画像の遅延読み込みといい、スクロールして画像の近くになったら後から JavaScript であとから読み込むことです。なのでスクロールしない状態ではファーストビュー (Above the fold) より下にある画像は読み込まれないので、その分表示が速くなります。 PageSpeed Insights はファーストビューの画像のチェックしかしないようなので、「修正が必要」の勧告を回避できるのです。

画像遅延読み込みのライブラリと lazySizes を使う利点

Lazy Load (画像遅延読み込み)を実装するいろいろなライブラリがあります。

この中からレスポンシブイメージの srcset 属性にも対応している lazySizes という JavaScript ライブラリを使うことにしました。長所は以下のようです。

  • jQuery 不要なので動作が速い
  • レスポンシブイメージの srcset 属性にも対応
  • Google bot の場合はスクロールバーなしと判定しすべての画像を表示( SEO 的に有利)

→ lazySizes : GitHub : DEMO

(このサイトはまだ画像自体がレスポンシブイメージに対応していませんが、近いうちに対応する予定です。テスト環境ではレスポンシブイメージをきちんと読み込んでいるのを確認しました。)

lazySizes は Google bot にも認識される

画像の遅延読み込みを行うと、 Google bot のようにスクロールせずに画面全体を一度にレンダリングすると、画像が読み込まれません。つまり画像が Google bot に認識されないわけで、 SEO 的に不利になるようです。 <noscript> タグを挿入して bot に画像を認識させようとする方法もあるようですが、 <noscript> タグ内に書かれていることが正しいのか判断できないため、 Google のジョン・ミラー氏曰くあまりおすすめできないようです。

→ Webmasters Stack Exchange : Lazy loading images and effects on SEO

lazySizes はブラウザがスクロール可能かどうか判断して、できない場合は即座にすべての画像を表示させるので、 Google bot にも認識されます。

WordPress のプラグイン Lazy Loading Responsive Images はあまりよろしくない

lazySizes を使った WordPress のプラグインに Lazy Loading Responsive Images というものがあるのですが、試してみたところいくつか問題がありました。例えばソースのコンテンツ部分が数値文字参照になってしまっていたり、ソースに無駄な <html> や <body> ダグが入ってしまい、それを回避するために JavaScript や CSS が加えてあるというあまりよろしくない仕様でした。また lazySizes は Google bot 対策もされているのに、 Bot 対策の <noscript> タグを挿入してしまいます。

Lazy Loading Responsive Images を修正して使う場合は最後に修正方法を記載してあります。

→ Lazy Loading Responsive Images のバグ修正

なので lazySizes のライブラリ Github からをダウンロードして、 function.php に簡単な関数を書いて動作させることにしました。

lazySizes を WordPress に組み込む

lazysizes.min.js をルート直下の js フォルダに置いてそこから読み込むようにしました。 lazySizes は <img> や <iframe> のクラス属性に lazyload を追加し、 src 、 srcset 、 sizes 属性の頭に data- をつけ一時的に退避させることで動作します。ブラウザがスクロールされると lazysizes.min.js が適宜 data- のついた属性を元に戻すことで画像を読み込みます。

ファーストビューに画像がある場合、 DOM の生成が終わってから読み込まれるので遅く感じることがあるので、 1 枚目の画像を LazyLoad の対象から外せるようにしました。また好みで URL に指定した文字列を含む画像を遅延読み込みの対象から外す指定ができるようにしました。

Ktai Style にて画像のリンクがうまく表示できていなかったので、 is_ktai() にてフューチャーフォンの場合はコードを変更するのをやめました。また一部エラーが出ていたので修正しました。

以前のコードは正規表現を多用していたので、動作を早くするためコードを見直しました。変えたところは img と iframe のタグを別々に取得していたのを一度で取得できるようにしました。複数の文字列を含むかどうかのチェックで正規表現を利用していたのを foreach でチェックするようにしました。正規表現で置換に利用しないものはキャプチャしないようにしました。また img と iframe の両方を合わせた中で画像が一番上にあった場合のみ、その画像を LazyLoad の対象から外すように仕様を変更しました。この変更でキャッシュ作成にかかる時間が 0.129 秒から 0.111 秒に 0.018 秒短縮されました。計測は以下のページで行いました。
ダイソン V6 Mattress+ の吸引力は嘘じゃない!

また以前のコードは以下に移しておきました。
古いコード: lazySizes を WordPress に組み込む

function add_lazysizes_js() {
	wp_enqueue_script( 'lazysizes', home_url() . '/js/lazysizes.min.js', array(), null, true );
}
add_action( 'wp_enqueue_scripts', 'add_lazysizes_js', 8 );

//クラスにlazyloadを追加
function add_lazyload_tag( $content ){
	if( empty($content) || is_feed() || is_admin() || is_ktai() ){
		return $content;
	}
	
	//1枚目の画像を除外するか指定(真偽値で指定)
	$exclude_first_img = true;
	
	//除外する画像を指定(配列で指定)
	$exclude_url_img = array('ad.jp.ap.valuecommerce.com');
	
	//iframeも対象にするか指定
	$include_iframe = true;
	
	//取得するタグの正規表現
	if( $include_iframe ){
		$tag_pattern = '/<(?:img|iframe)\s+.*?>/';
	} else {
		$tag_pattern = '/<img\s+.*?>/';
	}
	
	//コンテンツからimgの配列を作成
	preg_match_all( $tag_pattern, $content, $matches_img );
	
	$pattern_arr = array();
		
	foreach ( $matches_img[0] as $img_html ){
		//画像のとき
		if( strpos($img_html, '<img ' ) !== false ){
			if( empty($exclude_url_img) ){
				$pattern_arr[] = $img_html;
			} else {
				foreach( $exclude_url_img as $url ){
					if(strpos($img_html, $url) === false){
						$pattern_arr[] = $img_html;
					}
				}
			}
		} else { //iframeのとき
			$pattern_arr[] = $img_html;
		}
	}
	
	//iframeも含めて画像が一番上にあった場合除外
	if( $exclude_first_img && strpos($pattern_arr[0], '<img ' ) !== false ){
		array_shift($pattern_arr);
	}
	
	//lazySizes対応の属性に変更しclass属性にlazyloadを追加
	foreach ( $pattern_arr as $i => $img_html ){
		if ( strpos($img_html, ' class=') === false ){
			$subject_arr[] = preg_replace( array( '/(src|srcset|sizes)/', '/\s?\/?>/' ), array( 'data-$1', ' class="lazyload" />' ), $img_html );
		} else {
			$subject_arr[] = preg_replace( array( '/(src|srcset|sizes)/', '/(\s+?class=(?:"|\').+?)("|\')/' ), array( 'data-$1', '$1 lazyload$2' ), $img_html );
		}
	}
			
	$content = str_replace($pattern_arr, $subject_arr, $content);	
	
	return $content;
}
add_filter( 'the_content', 'add_lazyload_tag', 20 );

画像の遅延読み込み導入前後の PageSpeed Insights スコア

20170518_2.png

lazySizes を導入した前後で PageSpeed Insights のスコアはモバイルが 70 → 85 、パソコンが 74 → 94 と両方ともかなりよくなり、初めてグリーンになりました。画像は 1 つだけ遅延読み込みさせていないので、「画像を最適化する」の項目は残ったままですが、テストしたページが画像が多かったのでかなり効果はありました。

Fetch as Google で lazySizes の遅延読み込みがきちんと認識されるか確かめる

Fetch as Google でページが正しく bot に認識されているか確かめてみました。

20170518_3.png

こんな感じで画像もきちんと Google bot に認識されていることがわかります。 2 つ取得できなかったリソースがありますが、これは 1 つは Google AdSense の JavaScript で、もう 1 つはブログカードに表示されている favicon です。後者は対策しないといけませんね。

Lazy Loading Responsive Images のバグ修正

最後に結局使わなかったのですが、 Lazy Loading Responsive Images のソースを修正したので載せておきます。

このプラグインは PHP にてコンテンツを loadHTML() で DOM にしてソースをいじっているので、ソースが日本語が数値文字参照になってしまっているのです。ついでに the_content のフィルターフックを使っていてその前後に HTML の宣言がついてしまいます。なので、これらのバグを取り除きました。 80 行目の下に以下を追加します。

            $content = $dom->saveHTML();
            $content = mb_convert_encoding($content, 'UTF-8', 'HTML-ENTITIES');	//追加
            $content = preg_replace(array('/<!DOCTYPE.+?>\s*?<html>\s*?<body>/', '/<\/body><\/html>/'), array('', ''), $content, 1); //追加

またこの変更をすることで、プラグインより css や JavaScript を入れる必要がなくなったのでコメントアウトしました。

function lazy_load_responsive_images_script()
{
    wp_enqueue_script('lazy_load_responsive_images_script-lazysizes', plugins_url() . '/lazy-loading-responsive-images/js/lazysizes.min.js', '', "3.0.0", true);
    //wp_enqueue_style('lazy_load_responsive_images_style', plugins_url() . '/lazy-loading-responsive-images/css/lazy_load_responsive_images.css');
}

add_action('wp_enqueue_scripts', 'lazy_load_responsive_images_script', 20, 0);


function add_body_class_js_inline()
{
    echo '<script type="text/javascript">';
    echo "document.getElementsByTagName('body')[0].className+=' js'";
    echo '</script>';
}

//add_action('wp_footer', 'add_body_class_js_inline');

1 枚目の画像だけ LazyLaod の対象外にする場合は 41 行目あたりを下記のように変更します。

//foreach ($dom->getElementsByTagName('img') as $img) {
foreach ($dom-&gt;getElementsByTagName(&#039;img&#039;) as $i =&gt; $img) {	//追加
    if ($i === 0) {	//追加
        continue;	//追加
    }	//追加
    if (contains_string($img-&gt;getAttribute(&#039;class&#039;), &quot;lazyload&quot;) !== true)  {

古いコード: lazySizes を WordPress に組み込む

正規表現を多用したあまりパフォーマンスがよくないコード。

//lazySizesを読み込み
function add_lazysizes_js() {
	wp_enqueue_script('lazysizes', home_url() . '/js/lazysizes.min.js', array(), null, true );
}
add_action('wp_enqueue_scripts', 'add_lazysizes_js', 8);

//クラスにlazyloadを追加
function add_lazyload_tag($content){
	if(empty($content) || is_feed() || is_admin() || is_ktai()){
		return $content;
	}
	
	//1枚目の画像を除外するか指定(真偽値で指定)
	$exclude_first_img = true;
	
	//除外する画像を指定(配列で指定)
	$exclude_url_img = array('ad.jp.ap.valuecommerce.com');
	
	//iframeも対象にするか指定(真偽値で指定)
	$include_iframe = true;
	
	//コンテンツからimgの配列を作成
	preg_match_all('/<img\s+.*?>/', $content, $matches_img);
	
	//コンテンツからiframeの配列を作成
	if($include_iframe){
		preg_match_all('/<iframe\s+.*?>/', $content, $matches_iframe);
	} else {
		$matches_iframe[0][] = '';
	}
	
	$pattern_arr = array();
	
	//指定の画像を除外
	if(!empty($exclude_url_img)){
		$exclude_url = '/(';
		foreach($exclude_url_img as $url){
			$exclude_url .= $url . '|';
		}
		$exclude_url = rtrim($exclude_url, '|');
		$exclude_url .= ')/';
		
		foreach($matches_img[0] as $img_html){
			if(!(preg_match($exclude_url, $img_html))){
				$pattern_arr[] = $img_html;
			}
		}
	} else {
		$pattern_arr = $matches_img[0];
	}
		
	//1枚目の画像を除外
	if($exclude_first_img){
		array_shift($pattern_arr);
	}
	
	$pattern_arr = array_merge($pattern_arr, $matches_iframe[0]);
		
	//lazySizes対応の属性に変更しclass属性にlazyloadを追加
	foreach($pattern_arr as $i => $img_html){
		if(!(preg_match('/class=/', $img_html))){
			$subject_arr[] = preg_replace( array( '/(src|srcset|sizes)/', '/\s?\/?>/' ), array( 'data-$1', ' class="lazyload">' ), $img_html );
		}else{
			$subject_arr[] = preg_replace( array( '/(src|srcset|sizes)/', '/(.+?class=".+?)"/' ), array( 'data-$1', '$1$2 lazyload"' ), $img_html );
		}
	}
		
	$content = str_replace($pattern_arr, $subject_arr, $content);	
	
	return $content;
}
add_filter( 'the_content', 'add_lazyload_tag', 20 );

関連記事