Intersection Observer APIとは
Intersection Observer API(交差オブザーバー API )は、監視対象の要素(ターゲット要素)がビューポートまたは指定された要素(root要素)と交差した際に、それを検知してコールバックを呼び出せます。
交差の検知は、交差率というターゲット要素がどのくらい表示されたかを閾値(0.0~1.0)で指定できます。まだ交差していない状態で検知したい場合は、root要素を拡げるrootマージン(px値 or パーセント値)を利用することで検知できます。
Intersection Observer APIを使えば、スクロールやリサイズの度にイベントを発生させて、現在のスクロール位置とターゲット要素との差分を再計算する必要がなくなるようです!
Intersection Observer APIの使い方
① Intersection Observer(交差オブザーバー)の作成
コンストラクター関数で、オプションで指定した条件でターゲットが交差する度に、コールバック関数を渡すことでインスタンスを生成します。
const options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
const observer = new IntersectionObserver(callback, options);
settingsオプションについて
- root
ターゲットが見えるかどうかを確認するためのビューポートを指定。指定されなかった場合、または null の場合は既定でブラウザーのビューポートが使用されます。
- rootMargin
root要素の範囲を拡大・縮小するマージンを設けることができます。マージンはpx値かパーセント値が使用できます。'10px 20px 30px 40px' の場合、上, 右, 下 ,左 のマージンで、CSSと同じような形で指定できます。
- threshold
ターゲット要素がどのくらい見えた場合にコールバックを実行するかを指定します。既定値は0で1pxでも見えるとコールバックを実行します。1つ以上の数値を指定でき、 0.5の場合は50%見えたとき、[0.1, 0.25, 0.5, 1]と配列で複数を指定した場合だと、10%, 25%, 50%, 100%見えたときにコールバックを実行します。
② 監視対象を指定して、作成したオブザーバーに監視させる
const target = document.querySelector('#hogehoge');
observer.observe(target);
③ コールバック関数を作成する
コールバックは、IntersectionObserverEntry オブジェクトの配列(entries)とオブザーバー(observer)を受け取ります。
callback = (entries, observer) => {
entries.forEach((entry) => {
︙
});
};
tips_and_updatesIntersectionObserverEntryについて
IntersectionObserverEntryはIntersection Observer APIに用意されているインターフェイスで、各ターゲット要素と各ターゲットの交差状態を表します。このIntersectionObserverEntryのインスタンスが、コールバックにentriesの引数で渡されます。
お決まりごとなので、entriesで渡されると覚えておけば良いです。
tips_and_updatesIntersectionObserverEntryのプロパティ
- boundingClientRect
ターゲットのバウンディングボックス
- intersectionRatio
ターゲットがどのくらい見えているか、0.0 から 1.0 の数値。
ターゲットの面積が0の場合でも、0か1を返します。 - intersectionRect
ターゲットの交差している部分のバウンディングボックス
- isIntersecting
target 要素が交差状態に遷移したか (true) または交差状態から脱したか (false) のブール値。
- rootBounds
root要素のバウンディングボックス
- target
そのままターゲット要素(element)。
- time
文書の作成時刻を基準とする交差状態が発生した時刻
これで事前の下調べは完了です。実際に目次のカレント表示機能を書き直していきます。
Intersection Observer API を使った目次のカレント表示
▼ HTML
HTMLはこのブログ記事の構成を前提としています。
コンテンツのボディ部分はsectionで分けて、見出しにh2を設けており、これを目次に使用しています。h3など下のレベルの見出しは、目次には不要と思っていますので、単純なルールとなっています。
なお目次のlistはJavaScriptで生成しています。
▼目次部分(目次のlistはJavaScriptで生成)
<nav class="toc-nav">
<ul id="toc-nav-list">
</ul>
</nav>
▼コンテンツ部分
<section>
<h2>見出し①が入ります</h2>
︙
</section>
<section>
<h2>見出し②が入ります</h2>
︙
</section>
<section>
<h2>見出し③が入ります</h2>
︙
</section>
<section>
<h2>見出し④が入ります</h2>
︙
</section>
▼ JavaScript
▼目次のlistを生成
const contentsList = document.getElementById("toc-nav-list");
const headings = document.querySelectorAll("h2");
const tocFragment = document.createDocumentFragment();
for(let i = 0; i < headings.length; i++) {
const li = document.createElement("li");
const a = document.createElement("a");
const id = 'anchor' + (i+1);
sections[i].id = id;
li.classList.add('toc-nav-item');
a.classList.add('anchor');
a.textContent = headings[i].textContent;
a.href = '#'+id;
li.appendChild(a);
tocFragment.appendChild(li);
}
contentsList.appendChild(tocFragment);
▼目次のカレント表示
(コンテンツが見えているときに目次にクラスを付け、見えてないときはクラスを取る)
const sections = document.querySelectorAll("section");
const sectionsArray = Array.from(sections);
/* -- ① Intersection Observer(交差オブザーバー)の作成 -- */
const options = {
root: null, //ブラウザ画面をroot要素に
rootMargin: '-35% 0px -65%', //画面の垂直方向に交差監視する線を設けるイメージ(位置は適宜調整)
threshold: 0 //ターゲット要素が1pxでも交差したとき
}
const observer = new IntersectionObserver(tocAddCurrent, options);
/* -- ② 監視対象を指定して、作成したオブサーバーに監視させる -- */
sections.forEach(section => {
sectionObserver.observe(section);
});
/* -- ③ コールバック関数を作成する -- */
function tocAddCurrent(entries) {
entries.forEach(entry => {
const indexList = sectionsArray.indexOf(entry.target);
const currentList = document.querySelector(".toc-nav-item.is-active");
if (entry.isIntersecting) {
if (currentList !== null) {
currentList.classList.remove("is-active");
}
contentsList.children[indexList].classList.add("is-active");
}
});
}
ノンプログラマーながら何とか実装することができました。
カレント表示のスタイル(CSS)は割愛しています。
おわりに
目次のカレント表示は Intersection Observer API を使うことによって、従来のスクロールの度にイベント発火する必要はなくなりました!
ブログ記事は、上にスクロールした時、下にスクロールした時、それぞれに余計なスクロールエフェクトをつけているために、その部分はスクロールの度にイベント発火してます(泣)。
かなりブログも放置してしまったので、今年は一年で50記事ぐらいは書きたい。
この記事は昨年の下書き途中で放置したものを仕上げたもので、2023年一発目の記事とは言えない気分です。