画像の遅延読み込みは、かつてはlazysizes.jsプラグインを使ってましたが、最近はloading="lazy"やsrcset、sizesの組み合わせで事足りています。ただ、DOMを動的に生成してる場合や、ページによってサイズを変えたい場合など、複雑になると対応が困難になるため、JavaScriptで作ってみました。
以前にも取り上げたIntersection Observerを使います。
- Intersection Observerで、少しスクロールしたら現れるヤツ
- JavaScriptとcssで要素をふわっと表示させるアニメーション : Intersection Observer
- 交差オブザーバー API - MDN Web Docs - Mozilla
今回は例として、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のsrcset、sizesでなんとかなるレベルです。
パターン②複雑な形
画像のように、スマホ画面では画面いっぱい、タブレットでは2個~3個ずつ、PCでは3~4個ずつの場合。左右のpaddingは50pxずつ、計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の動画によっては大きいサイズのサムネイルが無い場合もあります。その場合、灰色のデフォルト画像になってしまいますので要注意。
サンプル
画面幅を変えてスクロールしてみてね!