Intersection Observer APIをもっとわかりやすく解説したい
インターセクションオブザーバーという、交差監視のJavascriptの解説記事を書いたのですが、自分で読み返してもイマイチわかりづらいので、もっとわかりやすく解説記事を書きたいと思います。(自分用)
前回のIntersection Observer APIの解説記事はこちらです。
コードの解説にフォーカスしすぎて、概念というか、考え方のようなものがすっぽり抜けているんですよね。
Intersection Observer APIの動作の仕組みや、基本的な使い方などは、上述した記事で解説しているので、まだ未読の方は一読をオススメします。
今回のゴールはこの動作を作ります
縦に並べられた写真が、スクロールすると大きくなる、という動作を設定します。
鳥の写真は静岡県にあるシャボテン公園で撮影したもの。
サンプルでやってること
Intersection Observerを使って、ビューポートの50%に入ったら、「sample_on」というクラスを付与し、外れたら外す、という動作をしています。
画像の拡大は付与された「sample_on」クラスがになっております。
やってることはいたってシンプル。
サンプルのHTMLコード
ulとliで画像をならべているだけです。
ulのIDにinter_ulを指定。
<ul id="inter_ul"> <li><img src="img/A7306414.jpg" width="760" height="760"></li> <li><img src="img/A7306532.jpg" width="760" height="760"></li> <li><img src="img/A7306550.jpg" width="760" height="760"></li> <li><img src="img/A7306739.jpg" width="760" height="760"></li> <li><img src="img/A7306740.jpg" width="760" height="760"></li> <li><img src="img/A7306763.jpg" width="760" height="760"></li> <li><img src="img/A7306880.jpg" width="760" height="760"></li> <li><img src="img/A7306893.jpg" width="760" height="760"></li> </ul>
サンプルのCSSコード
こちらもおもにレイアウト用のスタイルになっています。
#inter_ul { display: flex; flex-wrap: wrap; justify-content: center; margin: 20px auto; } #inter_ul li { list-style: none; width: 70%; text-align: center; transition: all 0.5s 0s ease; } /* ▼画像拡大用 */ #inter_ul li.sample_on { width: 100%; }
画像の大きさはliのwidthを拡大することで表現しており、liのCSSに「transition: all 0.5s 0s ease;」を設定してアニメーション効果を出しています。
サンプルのjavascriptコード
このjavascriptの役割は、スクロールに応じてクラスを付与、削除する、という役割です。
<script> //①ターゲット const target_item = document.querySelectorAll("#inter_ul li"); //②関数の呼び出し window.addEventListener("load", (event) => { //③の関数をここで呼び出し createObserver(); }, false); //③呼び出される関数 function createObserver() { let options = { root: null, rootMargin: "-50% 0px", threshold: 0 }; let main_observer; main_observer = new IntersectionObserver(photo_set, options); target_item.forEach( (elem) => { main_observer.observe(elem); }); } //④交差時のアクション function photo_set(entries){ entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add("sample_on"); }else{ entry.target.classList.remove("sample_on"); } }); } </script>
Intersection Observerに必要な物
サンプルを提示したところで、ここから解説パートに入りたいと思います。
Intersection Observerは交差監視なので、交差するもの(ターゲット)と、交差地点があります。
そして、交差した場合にさせる処理の3つから成り立っています。
ターゲットとは
今回のサンプルの交差するもの「ターゲット」は「li」タグになります。
正確には「#inter_ul」IDの子要素の「li」タグ全てです。
このターゲットは、もちろんliタグ以外も使えます。
javascriptで指定できるDOM要素であれば、なんでもOK。(多分)
前回の例では、ファーストビューの画像がターゲットでした。
今回は複数ターゲットの事例で紹介しています。

//①ターゲット const target_item = document.querySelectorAll("#inter_ul li");
↑javascriptのコードでいうとこれ。
このターゲット要素が交差エリアに入ったら、クラスを付与するわけですね。
交差エリアについて rootMargin
rootMarginの考え方が結構わかりづらく、色々実験してようやく把握できました。
//③呼び出される関数 function createObserver() { let options = { root: null, rootMargin: "-50% 0px", threshold: 0 }; let main_observer; main_observer = new IntersectionObserver(photo_set, options); target_item.forEach( (elem) => { main_observer.observe(elem); }); }
↑このコードの5行目、optionsの中にあるrootMarginです。
rootMarginの書き方について
rootMarginには必ず、px か % をつける必要があります。0でも0pxとする必要があります。
この「値」は上・右・下・左の4個所を指定できます。
CSSと同じ形式でショートハンドでも記述が可能です。
-(マイナス)のネガティブマージンの指定もできます。
但し、vwやvh、remなどは使えません。単位は「px」or「%」 です。
サンプルでやってる-50%について
サンプルでは上下に対して-50%を指定しています。
簡単に一言で言うと、表示エリアの上下から真ん中を指定しています。
rootMargin-50%をより詳しく解説
rootMargin:-50%を図解にするとこうなります。

上下から-50%とっているので、ちょうど真ん中に一本の線が引かれた交差地点になります。
サンプルは大きさを変更しているのでちょっとわかりづらいのですが、注意してみるとビューポートの真ん中を超えると発動しているのがわかります。
発動条件の確認
ターゲットアイテムが交差地点に入ったら、sample_onクラスが付与され、画像が拡大します。

これはターゲット要素の上端が入った時点で発動します。
※発動条件によりますが今回のサンプルは。
//④交差時のアクション function photo_set(entries){ entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add("sample_on"); }else{ entry.target.classList.remove("sample_on"); } }); }
今回のサンプルのクラス付与の条件は、
上記コードの4行目、if (entry.isIntersecting) で設定しています。
終了条件の確認
交差地点を抜けるタイミングについて確認します。
発動とは異なり、要素の下端が抜けたら終了になります。(クラスが解除される)

交差地点をこえると、if (entry.isIntersecting) がfalseになり、クラスがremoveされると。
サンプルの動作について
サンプルの場合、このrootMarginが狭く中心のみになっているため、ターゲットが1つしか発動しないわけです。
このrootMarginの値を調整すると、発動のタイミングがずれて、複数の画像が拡大されるようにもできます。
rootMarginの交差地点の条件色々
rootMarginの値が変わるとどうのように変化するかを図にしてみました。
rootMargin:0pxの場合
一番スタンダードな使い方の0pxの場合は、ビューポート全体が交差地点になります。

表示エリアに入った時点で発動し、エリア外に行くと解除されます。
rootMargin:-20% 0pxの場合
上下に20%分のマージンが生まれます。
ちょっと(20%)スクロールした時点で発動すると。

0pxだと、スクロールによる変化が感じられないので、変化を視認できるようにしたい場合は、ある程度マージンを持たせると良いですね。
上下で異なる値を指定した場合 rootMargin:-20% 0px 0px 0px
上右下左で指定できるので、上だけ-20%、下は0pxという指定もできます。

この場合、発動は少しスクロールしてから、解除はエリアを抜けたら、という動作になります。
入りは動くけど、出るときはそのままクラスを持たせたいときなどは、このような指定になりますね。
※ビューポートの外にいくとクラスは解除されます。
発動条件のif文について
今回、発動条件にしているのは if (entry.isIntersecting) にしています。
前回の記事ではif (entry.intersectionRatio <= 0) を条件にしていました。
//④交差時のアクション function photo_set(entries){ entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add("sample_on"); }else{ entry.target.classList.remove("sample_on"); } }); }
entry.はforEachのコールバック関数の引数です。
entryの中には、IntersectionObserverの色々な情報が含まれています。
entryの上にいる、entries.forEachのentriesには、ターゲットになる8つのliタグが配列で入っています。
entriesがどこから来たかというと、photo_set関数の引数です。
photo_set関数は、function createObserver()内で呼び出されており、function createObserver()のコールバック関数です。
コールバック関数として呼び出されたphoto_set()の引数には、IntersectionObserverEntryが渡されます。
ちなみにphoto_set()は私が勝手に命名しているだけで、どんな名前を付けても問題ありません。
callback()と命名して使うのが一般的なような気がします。
//③呼び出される関数 function createObserver() { let options = { root: null, rootMargin: "-50% 0px", threshold: 0 }; let main_observer; main_observer = new IntersectionObserver(photo_set, options); target_item.forEach( (elem) => { main_observer.observe(elem); }); }
entry.isIntersectingとは
ターゲットが交差地点に入ったら「true」を返し、交差地点外にいるときは「false」を返すブール値です。
交差地点にターゲットが触れたらtrueになり、クラス付与がされ、交差地点を超えたらfalseになり、クラスが外れます。
entry.intersectionRatioとは
こちらはターゲットが交差地点にどれだけ入っているかを0~1の数値で返します。
0 or 1 ではなく、0~1の小数点以下で返ってくるので細かく調整が可能です。
コードの流れを図解で整理
言葉だけだとよくわからないので流れを図解にしました。

図解にしてもわかりやすいとはいいがたいのですが、頭で理解するより、使ってみて慣れる方が簡単です。
散々説明したあとにこういう言い方もあれなんですが、とりあえず作って動かしてから、理屈を理解する方が習得が早いと思います。
Intersection Observer API まとめ
今回のサンプルを私の仕事であるWEBページ制作で実際に使ったんですが、作れたもののイマイチ理解しきれていないを痛感し、記事にしました。
1枚ずつ拡大する際に、どこを調整するのかよくわかってなくて、試行錯誤(適当とも呼ぶ)した結果、思い通りに動きを再現できたのですが、なぜこうなるのかがわかっていなかったと。
冒頭に(自分用)と記したとおり、自分のための記事であります。
rootMarginだけじゃなく、threshold: 0をいじっても調整できると思うのですが、今のところ事足りてるので、いいかなと思ってます。
小難しい内容を解説するのは非常に時間がかかるのですが、自分の頭の整理になるので、今後もなにかあったらまとめたいと思います。
Intersection Observer APIは使い方次第でいろいろと面白いことができそうなので、今後も積極的に使っていきたいと思っています!