この記事ではJavaScriptのIntersectionObserverを使った画像の遅延読み込みについて、
メリットから実装方法までわかりやすく解説します。
この記事でわかること
- そもそも遅延読み込みって何?
- 画像の遅延読み込みが必要な理由
- <img>タグにloading=”lazy”を指定するだけじゃダメなの?
- intersectionObserverを使用した遅延読み込みの実装方法
コードだけ欲しい方はこちらから
余白や画像サイズは適宜調整してご利用ください。
<section class="lazyload">
<h2 class="lazyload__title">画像の遅延読み込み</h2>
<p class="lazyload__txt">指定した領域内に入ったら画像が表示されます</p>
<div class="lazyload__inner">
<div class="lazyload__img-wrap">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==" data-src="https://placehold.jp/240x160.png" alt="" width="240" height="160" class="img">
</div>
<div class="lazyload__img-wrap">
<p>ここに画像が出てきます</p>
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==" data-src="https://placehold.jp/240x160.png" alt="" width="240" height="160" class="img">
</div>
<div class="lazyload__img-wrap">
<p>ここに画像が出てきます</p>
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==" data-src="https://placehold.jp/240x160.png" alt="" width="240" height="160" class="img">
</div>
<div class="lazyload__img-wrap">
<p>ここに画像が出てきます</p>
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==" data-src="https://placehold.jp/240x160.png" alt="" width="240" height="160" class="img">
</div>
</div>
</section>
.lazyload {
&__inner {
max-width: 900px;
margin: 0 auto;
}
&__title {
font-size: 32px;
width: fit-content;
margin: 30px auto 0;
}
&__txt {
font-size: 24px;
width: fit-content;
margin: 15px auto 0;
}
&__img-wrap {
margin-top: 100px;
&:not(:first-of-type) {
margin-top: 600px;
}
.img {
width: 240px;
aspect-ratio: 960/640;
}
}
}
//監視対象
const imgs = document.querySelectorAll("img.img");
//実行する関数の定義
const replaceImage = (entries, observer) => {
entries.forEach(({ isIntersecting, target }) => {
if (!isIntersecting) return;
target.src = target.dataset.src;
observer.unobserve(target);
});
};
//監視オプション
const options = {
root: null,
rootMargin: "0px",
threshold: 0.8
};
//インスタンス化
const replaceImageObserver = new IntersectionObserver(replaceImage, options);
//監視の実行
imgs.forEach((img) => {
replaceImageObserver.observe(img);
});
そもそも遅延読み込み(lazy load)とは?
遅延読み込みとは、
「今すぐ表示する必要がない要素は、あとでゆっくり読み込む」ことです。
FVなどのサイトを訪れて最初に表示される要素を除き、ユーザーが見るときに要素が全て表示されていればそれでOKですよね?
後でできることは後でやろう、というのがlazy loadの意味です。
なぜ画像の遅延読み込みが必要?
一生懸命準備した良質なコンテンツを見てもらうための工夫の1つとして画像の遅延読み込みが必要です。
具体的には、「ページを表示する速度の改善」のためです。
Webサイトを構成する要素のうち、容量が特に大きくなりがちなのが画像ファイル。
容量が大きくなると、その分ページの読み込みに要する時間が長くなります。
なかなかページが表示されないと、ブラウザバックしたくなりませんか?
反対に言えば、読み込む容量を少なくすれば、読み込みに要する時間を短縮することができますよね。
そこで、遅延読み込みを実装して読み込み時間を短縮することで、ユーザーの離脱を防止しようということです。
<img>タグにloading=”lazy”を指定するだけじゃダメ?
結論、特に細かい制御が求められていない箇所ならloading=”lazy”の設定でOKだと思います。
なんと言ってもたった一行で終わるのでお手軽そのもの。
ただし、ブラウザによってどの程度の領域までスクロールすれば表示されるのか等、ばらつきがあることに注意が必要です。どのタイミングで画像を表示するのか、コンテンツ内で表示される順番を制御したい等の要望には応えることにはできません。
また、設定できるタグは<img>と<iframe>に限られます。
IntersectionObserverとは場面によって使い分けが必要になりそうですね。
・細かい制御がなければloading=”lazy”の指定でOK
・ただし、<img>と<iframe>にしか使用できない
IntersectionObserverが必要な理由
ここまで読んでいただければ、すでにお分かりですね!
IntersectionObserverが必要な理由は、
- 出現タイミングや順番の細かい制御が必要な場合
- <img>・<iframe>以外のタグにも、特定の領域に入ったら現れるという制御を行いたい
実際の実務でも高頻度で実装に使用しています。
このAPIの存在を知らない頃は、スクロール量やコンテンツの位置を計算してアニメーションの制御やコンテンツの表示・非表示を切り替えていました。
こんな面倒な実装はもう二度とやりたくないですね…
IntersectionObserverとは
いよいよ本題のIntersectionObserverです。
そもそもIntersectionObserverとは、その名のとおり「交差」を「見ているもの」。
指定したターゲットが指定した領域と交差したかどうかを判定してくれます。
IntersectionObserver = 「見張り番の人」と覚えてください。
では、何がどう交差したら「交差したよ」と教えてくれるのでしょうか?
下の図をご覧ください。

スクロールにより、表示領域が下に移動します。
そうして、上図の表示領域(=root)がターゲット(監視対象)にくっついたら、「交差した!」と教えてくれます。(下図参照)
この、「交差した」瞬間に、画像を表示したり、アニメーションを開始したりするよう制御ができるのです。

非対応ブラウザ
主な非対応ブラウザは以下の3つ。
ただし、これらのブラウザを使用している人はユーザーの1%にも満たないです。
ほぼ全ての人の環境で使用できると思ってもらえてOK!
・Internet Explorer(IE)
・Android4系以前のAndroidブラウザ
・iOS Safari 11以前
実装の流れとコード解説
まずはデモページをご覧ください。
下にスクロールしていくことで、画像が表示されていく様子が確認できます。
実装の流れ
- <img>のsrc属性に軽量のダミー画像をセット
- data-src属性に実際に表示する画像をセット
- ターゲット(監視対象)のDOMを取得
- IntersectionObserverで実行する関数の定義
- optionの定義
- IntersectionObserverのインスタンス化
- ターゲットを監視
コードの解説
〇HTML
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="
data-src="実際の画像パス" width="画像の幅" height="画像の高さ">
src属性にセットしている画像は1px × 1pxの透明画像です。
使用する際には、レイアウト崩れ防止のためにCSSで実際の画像の高さと幅を設定しておくと良いです。
〇JavaSctipt
window.addEventListener('DOMContentLoaded', () => {
//監視対象のDOMを取得
const imgs = document.querySelectorAll('img.img');
//実行する関数の定義
const replaceImage = (entries, observer) => {
entries.forEach(({ isIntersecting, target }) => {
if (!isIntersecting) return;//交差していないときは↓の処理をせず終了
target.src = target.dataset.src;//src属性をdata-src属性に置換
observer.unobserve(target);//監視対象の監視をやめる
})
}
//監視オプション
const options = {
root: null,
rootMargin: '0px',
threshold: 1,
}
//インスタンス化
const replaceImageObserver = new IntersectionObserver(replaceImage, options);
//replaceImageObserverは任意の名前でOK
//監視の実行
imgs.forEach(img => {
replaceImageObserver.observe(img);
//intersectionObserverのobserveメソッドを実行し、ターゲットの監視を開始
})
});
実行する関数で引数に定義している”entries”は、ターゲット(監視対象)の情報が詰まった配列です。
この”entries”でよく使用する要素である、”isIntersecting”と”target”を分割代入でforEachの引数に指定しています。
・isIntersecting ⇒ 交差しているかどうかの判定。交差している場合に”true”を返す
・target ⇒ 交差しているターゲットのDOM情報(取得した<img>のうち、実際に交差しているもの)
IntersectionObserverのオプションとして、3種類の値を設定できます。
- root
-
見張り番がどこを見張るかののぞき窓のこと。nullを指定するとviewportがのぞき窓になる。
こののぞき窓の範囲が交差を判定できる範囲になります。 - rootMargin
-
のぞき窓のフチを広げたり縮めたりする余白の設定のこと。
例えば、のぞき窓の下の余白を50%縮めたい場合、
rootMargin:’0px 0px -50% 0px’
というように指定します。CSSでのmarginの指定と同じ書き方です。
こすうることで、ターゲットが表示領域のより内側に来てから交差判定されることになります。 - threshold
-
どれくらい見えたら反応するかの見え具合の基準。
rootとrootMarginはのぞき窓側のオプションでしたが、thresholdは監視されるターゲット側のオプションです。
この値は0~1の範囲で指定可能。- 0を指定 ⇒1ピクセルでもrootと交差したら「交差!」と判定
- 0.5を指定 ⇒ 要素の半分がrootと交差したら「交差!」
- 1を指定 ⇒ 要素のすべてがrootと交差したら「交差!」
root,rootMargin,thresholdの3つのオプションを上手く組み合わせることで、画像の出現タイミングやアニメーションの実行タイミングを細かく制御することができるようになるのです。
まとめ
・画像の遅延読み込みはサイトの読み込みスピードを上げて、ユーザーにコンテンツを届けるための工夫の1つ
・遅延読み込みの実装は、loading=”lazy”の指定 or IntersectionObserverの利用
・細かい制御をしたいときはIntersectionObserverを利用