ブルーシュ

IT技術の勉強記録

ホーム > Tips > フロントエンド

Intersection Observerで画像の遅延読込とレスポンシブ

, , ,

画像の遅延読み込みは、かつてはlazysizes.jsプラグインを使ってましたが、最近はloading="lazy"srcsetsizesの組み合わせで事足りています。ただ、DOMを動的に生成してる場合や、ページによってサイズを変えたい場合など、複雑になると対応が困難になるため、JavaScriptで作ってみました。
以前にも取り上げたIntersection Observerを使います。

今回は例として、YouTubeのサムネイル画像を使用します。

画像URL サイズ 横幅(px) * 縦(px)
https://i.ytimg.com/vi/動画ID/maxresdefault.jpg 1280 * 720
https://i.ytimg.com/vi/動画ID/sddefault.jpg 640 * 480
https://i.ytimg.com/vi/動画ID/hqdefault.jpg 480 * 360
https://i.ytimg.com/vi/動画ID/mqdefault.jpg 320 * 180
https://i.ytimg.com/vi/動画ID/default.jpg 120 * 90

これらをページやブロックによって分けて表示させます。

HTML(PHP)側


<div id="ytwrap">
<?php foreach($video_ids as $video_id): ?>
<a class="yt" href="https://www.youtube.com/watch?v=<?php echo $video_id; ?>">
	<img data-video-id="<?php echo $video_id; ?>" class="ytimg" src="https://i.ytimg.com/vi/<?php echo $video_id; ?>/mqdefault.jpg" width="480" height="360" loading="lazy">
</a>
<?php endforeach; ?>
</div>

ポイントはimgタグにデータ属性としてdata-video-id="動画ID"を仕込んでいる点です。

JavaScript側

パターン①基本形

<script type="text/javascript">
	const targets = document.getElementsByClassName('ytimg'),
	callback = (entries, observer) => {
		entries.forEach(e => {
			if (e.isIntersecting) {
				const videoId = e.target.dataset.videoId,
						mq = "https://i.ytimg.com/vi/"+videoId+"/mqdefault.jpg",
						hq = "https://i.ytimg.com/vi/"+videoId+"/hqdefault.jpg",
						sd = "https://i.ytimg.com/vi/"+videoId+"/sddefault.jpg";

				if(window.matchMedia( "(max-width: 320px)" ).matches) {
					e.target.src = mq;
				} else if(window.matchMedia( "(max-width: 480px)" ).matches) {
					e.target.src = hq;
				} else if(window.matchMedia( "(max-width: 640px)" ).matches) {
					e.target.src = sd;
				}
				//・・・続く
			}
		});
	};
for(let i = targets.length; i--;){
	let observer = new IntersectionObserver(callback);
	observer.observe(targets[i]);
}
</script>

imgタグに仕込んでおいた動画IDをdatasetで取得しています。
データ属性について:データ属性の使用 - ウェブ開発を学ぶ - MDN Web Docs
ここまでは普通ですね。HTMLのsrcsetsizesでなんとかなるレベルです。

パターン②複雑な形

画像のように、スマホ画面では画面いっぱい、タブレットでは2個~3個ずつ、PCでは3~4個ずつの場合。左右のpadding50pxずつ、計100pxとします。

画面幅によって画像の表示サイズを変える

<script type="text/javascript">
const targets = document.getElementsByClassName('ytimg');
for(let i = targets.length; i--;){
	let observer = new IntersectionObserver((entries, observer) => {
		for(const e of entries) {
			if (e.isIntersecting) {
				const videoId = e.target.dataset.videoId,
						mq = "https://i.ytimg.com/vi/"+videoId+"/mqdefault.jpg",
						hq = "https://i.ytimg.com/vi/"+videoId+"/hqdefault.jpg",
						sd = "https://i.ytimg.com/vi/"+videoId+"/sddefault.jpg";

				if(window.matchMedia( "(max-width: 420px)" ).matches) {
					//スマホ1列 320pxの画像が表示される
					e.target.src = mq;
				} else if(window.matchMedia( "(max-width: 580px)" ).matches) {
					//スマホ1列 480pxの画像が表示される
					e.target.src = hq;
				} else if(window.matchMedia( "(max-width: 600px)" ).matches) {
					//スマホ1列 630pxの画像が表示される
					e.target.src = sd;
				} else if(window.matchMedia( "(max-width: 741px)" ).matches) {
					//タブレット画面
					//1~2番目・それ以降も320pxの画像が表示される
					e.target.src = mq;
				} else if(window.matchMedia( "(max-width: 1061px)" ).matches) {
					//タブレット画面
					if(i < 2){
						//1~2番目 480pxの画像が表示される
						e.target.src = hq;
					} else{
						//3番目以降 320pxの画像が表示される
						e.target.src = mq;
					}
				} else if(window.matchMedia( "(max-width: 1200px)" ).matches) {
					//タブレット画面
					if(i < 2){
						//1~2番目 640pxの画像が表示される
						e.target.src = sd;
					} else{
						//3番目以降 480pxの画像が表示される
						e.target.src = hq;
					}
				} else if(window.matchMedia( "(min-width: 1201px)" ).matches) {
					//PC画面
					if(i < 3){
						//1~3番目 480pxの画像が表示される
						e.target.src = hq;
					} else{
						//4番目以降 320pxの画像が表示される
						e.target.src = mq;
					}
				}
			}
		}
	});
	observer.observe(targets[i]);
}
</script>

cssはこんな感じ

/*スマホ*/
#ytwrap{
	display:grid;
	grid-template-columns: 1fr;
	gap:10px;
}.ytimg{
	width:100%;
	height:auto;
}
/*タブレット*/
@media (min-width: 600px) {
	#ytwrap{
		grid-template-columns: repeat(6, 1fr);
	}
	/*タブレットの2番目まで*/
	.yt:nth-of-type(-n+2){
		grid-column: span 3;
	}
	/*タブレットの3番目以降*/
	.yt:nth-of-type(n+3){
		grid-column: span 2;
	}
}
/*PC*/
@media (min-width: 1201px) {
	#ytwrap{
		grid-template-columns: repeat(12, 1fr);
	}
	/*PCの3番目まで*/
	.yt:nth-of-type(-n+3){
		grid-column: span 4;
	}
	/*PCの4番目以降*/
	.yt:nth-of-type(n+4){
		grid-column: span 3;
	}
}

結果

Chromeのデベロッパーツール⇒Networkのタブをクリック。スクロールして画面サイズに合致すれば適切なサイズの画像が読み込まれることが確認できます。PageSpeed Insightsでも、体感でも、かなりスピードアップしました。
YouTubeの動画によっては大きいサイズのサムネイルが無い場合もあります。その場合、灰色のデフォルト画像になってしまいますので要注意。

サンプル

画面幅を変えてスクロールしてみてね!

Intersection Observerで画像の遅延読込とレスポンシブ #フロントエンド #ウェブデザイン #ウェブ制作 #WEBデザイン #WEB制作 #レスポンシブ #intersectionobserver #grid #YouTube

作者の似顔絵

プログラミング歴19年🌈調べたことをブログにまとめていきます。
記事の感想・質問・間違い指摘などはツイッター ( @blooshcompany ) へお願いします。

秋田のウェブ活用をサポート

ホームページを自作してコスト削減!秋田の事業者は無料で利用できます。
ネットショップ・WordPress・SEO対策などさまざまなお悩みをサポートします。

その他、フロントエンド案件のご依頼はインフォメーション

秋田市 レンタル着物 笹パンダ堂のバナーリンク