Studio
Happyvalley

妃魔人からひと言遅いことが
いいこともあるよ

画像の遅延読み込みを
極める

image: 妃魔人からひと言遅いことがいいこともあるよ
Sometimes Its Good To Be Late.

画像遅延読み込みをオリジナルで実装

2022/03/01

ファーストビュー以外は後で表示した方がいいんだって

だれでも一度は試したことがある、GoogleのPageSpeed Insights。
必ずでる警告のひとつが画像遅延読み込みです。
一昔前はCSSスプライトなどといってロールオーバー画像を一枚にまとめたりしたものですが・・・今では今必要じゃないなら後で読み込めといわれます。
jQuery も重いいといわれたり、fontawesomeも同じく・・・
要はファーストビューを早く表示させろってことでGoogleのいいたいことは理解できます・・・。
今はもう両者とも使用してません。

というわけでいろいろ調べたところ、ブラウザにloading属性追加で遅延読み込みするようです。
クロム、firefoxと最新のスマホはすでに対応しているようですね。
スマホもアップデートすれば対応するようですね。iOS15.4で機能しているのを確認しました。

loading属性追加で遅延読みしたサンプル:
https://labo.studio-happyvalley.com/imgdecode/att_loading/


一応、Googleですべてのブラウザ向けの対策方法を掲載しています。まだ、ブラウザがすべて対応していないころの対策です。
javascriptのlazysizesを使う方法で私も試してみました。

たしかに画面に表示されていない画像の読み込みをストップさせます。
しかし理屈はimg要素のソース属性を空っぽにして無理やり読み込ませなくしているだけなんで、src属性がからっぽなのは気持ち悪いです。

Google推奨のlazysizesは準備が超面倒くさいなあ

以下がその記述例です。

<img width="800" height="533" 
class="lazyload" 
data-src="/assets/labo/lazyload/images/flower2.webp" 
alt="flower's picture" />

なにより手作業で新たにdata属性を追加したりsrc属性を削除しなくていけないのがかなり面倒ですね。

とにかくこのコーナーのコンセプトは面倒なことはぜ~んぶプログラムに任せて楽ちんしようってコンセプトなんで、手作業でいちちソースいじったり属性追加したりクラス追加するのはナンセンス。
こんなスマートじゃないJSは却下!

Google推奨の画像遅延読み込みサンプル:
https://web.dev/codelab-use-lazysizes-to-lazyload-images/


loading属性がベストプラクティスな気がしますが、いくつか問題に感じる箇所があります。

1. 初期表示で表示される箇所とされない箇所の判別は手動でやらないといけない
 ここが一番ネックですね。属性値に「auto」もあるようですが・・。

2.やはりimg要素へコードを書き込むのは面倒。新設の場合はいいけど。

3. 画像を読み込むタイミングの問題。たとえばすごく重い画像の場合、表示領域に入ってきても
 瞬時に読み込まれないと高さと横幅がゼロのままということになります。
 その後読み込まれるときにレイアウトがガクッと変化して、いわゆるCLS値が悪くなります。

この辺考えるとJSで好きなように作るのが一番いいような気がします。

画像遅延システムを新たにJSで開発

ようするに画面以外の画像の読み込みをストップして、表示されるようになったら読み込めばいいだけの簡単な仕掛けなのでJSで書いてみました。

全ての画像を読み込んで、各々のブラウザ下端までの高さを取得しそれがゼロ近くになったら表示させます。
表示させない仕組みはsrc属性値を空っぽはあんまりなんで、自動でダミー画像か低解像度画像と差し替え、本来のURIはデータ属性などに保存します。その後表示するときにそのURIをsrc属性と差し替えます。

https://studio-happyvalley.com/wp/wp-content/uploads/Image-5-1.png

画面に表示されない画像は60x40の極小サイズを表示してローディング負荷を軽くし、img要素の縦横サイズはオリジナルのサイズで差しかえ。これで実際に表示されるときに画像の大きさが変化しないのでCLSを解除できます。

自作の画像遅延読み込みJSコード:
https://labo.studio-happyvalley.com/imgdecode/headerop/

const i={
	wW:window.innerWidth,
	wH:window.innerHeight,
	_itemName: '',
	passive: {passive : true},
	get items(){ 
		const v= this._itemName ? this._itemName : 'img.def';
		return document.querySelectorAll( v );
	}
};

window.addEventListener('load', setImg, i.passive);
window.addEventListener('scroll', setImg, i.passive);

function setImg(e){

i.items.forEach(function(item, num){
	const H= item.getBoundingClientRect().top; 
		  console.log(`
H:  ${H}
wH:  ${i.wH}
	  `);
	 if(i.wH > H){	
		const par= item.parentElement;
		const imgpath= item.src;
		const imgW= item.width;
		const imgH= item.height;
		const pathArray= imgpath.split('/');
		const imgfile= pathArray.pop();
		const filename= imgfile.split('.')[0];
		const path= pathArray.join('/');
		const newfilename= path + '/' + filename + '_hd.webp';

		const img = new Image();
		img.src = newfilename;
		img.width= imgW;
		img.height= imgH;
		img.decode()
		.then(() => {
			par.removeChild(item);
			par.appendChild(img);
		  console.log(`
imgpath:  ${imgpath}
newfilename:  ${newfilename}
	  `);
	})
		.catch((encodingError) => {
		  console.log(`
no render:  ${img.src}
		  `);
		})
	}
});
}

試しにPageSpeed Insightsでテストしたら満点でした。まあ、かんたんなHTMLなので当然といえば当然ですが。

※javascriptだけっていいましたが、実はダミー画像を表示する時、縦横がダミー画像のサイズになります。つまり本番画像を表示させると急激にサイズが変わってしまいます。
そのためPHPを使ってオリジナルの画像の縦横をあらかじめ取得しその値を設定しています。これをやらないとさっき話したようにCLS値が大きくなってしまいます。
まあPHPが面倒であれば、あらかじめ本画像の縦横を決め込んで手作業で記述してもいいですが。
レスポンシブで縦横比が違う画像を表示する場合はやはりCLS値が悪くなります。
このプログラムは使用する画像を動的に読み込むので縦横比は同じです。

※ただし、GoogleのPageSpeed Insightsの点数だけを考慮するなら、サイズをオリジナルに置き換える必要はありません。オリジナル画像が読み込まれるのはスクロールしてからなので、あくまで本質のユーザービリティ高くするための施策です。

ちなみにこのサイトもTOPにこのシステムを実装してます。
GoogleのSpeedinsightsでもそれなりのいい値をだします。

Google SpeedInsights でStuido Happyvalley を検証する:
https://pagespeed.web.dev/report?url=https%3A%2F%2Fstudio-happyvalley.com%2F&hl=ja

低解像度と本データの読み込みの確認

テスト用に本データ(8000px * 12023px)と低解像度(27px * 40px)を用意。実際に意図通りに処理できるか確認したサンプルのスクリーンショットです。
テストのためあえて1万ピクセルの画像を使用しています。通常の100倍の重さの画像です。
なので表示されるとき数秒掛かります。

なんで100倍の重さの画像にしたかというと、この画像差し替えJSの肝は、低解像度(27px * 40px)画像をオリジナル画像の縦横サイズで表示するところにあります。
ただ、このオリジナル画像を解析するときに実はオリジナル画像を読み込んでいるのじゃないかという疑念が起きたんですね。
もしそうだとすると何のために画像差し替えてるのか意味がないので。
もし、想定通り画像情報から縦横比を取得しているのであれば読み込み時間は瞬時に表示。懸念しているようにオリジナルサイズを読み込んでいるのなら2~3秒かかるはずですし、PageSpeed Insightsdeでもアラートが出るはずです。
といいうのがこのテストの理由です。

fig1.webp

fig2.webp

低解像度と本データの差し替え読み込みの確認サンプル:
https://labo.studio-happyvalley.com/readyState/php/

『妃魔人からひと言遅いことがいいこともあるよ』関連のお薦め

このページで紹介しているプログラムやビジュアルなどご依頼いただければ実装を賜ります。
お問い合わせはこちら