Interseprtion Observerを解読する

ちゃんと理解していないのに使ってしまいました...Interseprtion Observer。

このまま年を越すわけにはいかないと思ったので、2020年の力試しとしてまとめます。

2020年最後の記事です。

Interseprtion Observerとは

とてもわかりやすく、詳しく書かれているので、こちらを見て分かる方は十分です。

JSでのスクロール連動エフェクトにはIntersection Observerが便利 - ICS MEDIA

サンプルコードと動きの概要

上記記事から引用させていただきます。
文字通り「解読」なのでこの詳しい記事をさらに細かく見ていきます!

JavaScript

// 今回の交差を監視する要素
const boxes = document.querySelectorAll(".box");

const options = {
  root: null, // 今回はビューポートをルート要素とする
  rootMargin: "-50% 0px", // ビューポートの中心を判定基準にする
  threshold: 0 // 閾値は0
};
const observer = new IntersectionObserver(doWhenIntersect, options);
// それぞれのboxを監視する
boxes.forEach(box => {
  observer.observe(box);
});

/**
 * 交差したときに呼び出す関数
 * @param entries
 */
function doWhenIntersect(entries) {
  // 交差検知をしたもののなかで、isIntersectingがtrueのDOMを色を変える関数に渡す
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      activateIndex(entry.target);
    }
  });
}

/**
 * 目次の色を変える関数
 * @param element
 */
function activateIndex(element) {
  // すでにアクティブになっている目次を選択
  const currentActiveIndex = document.querySelector("#indexList .active");
  // すでにアクティブになっているものが0個の時(=null)以外は、activeクラスを除去
  if (currentActiveIndex !== null) {
    currentActiveIndex.classList.remove("active");
  }
  // 引数で渡されたDOMが飛び先のaタグを選択し、activeクラスを付与
  const newActiveIndex = document.querySelector(`a[href='#${element.id}']`);
  newActiveIndex.classList.add("active");
}

これはスクロールに合わせて目次の見出しに背景色がつくといったものです。
簡単にHTML+CSSの様子を表すと、こうなります。

f:id:yokoyoko_115:20201223005340j:plain

白い部分がビューポート(ディスプレイ)です。
#indexList というところが目次です。

.box を監視して、ビューポートに表示されるとその .box に紐付いた目次の見出しに.active のクラスが付きます。

解読

まとまりごとに見ていきます。

// 今回の交差を監視する要素
const boxes = document.querySelectorAll(".box");

const options = {
  root: null, // 今回はビューポートをルート要素とする
  rootMargin: "-50% 0px", // ビューポートの中心を判定基準にする
  threshold: 0 // 閾値は0
};

監視要素の.boxboxes に入れる。
querySelectorAll メソッドは指定したセレクタのHTML要素(NodeList)を取得します。
boxesを console.log(boxes) とすると、

NodeList(4) [div.box, div.box, div.box, div.box]

などと出てきます。

そして options というオブジェクトを作る。
{} で囲まれているのでオブジェクトです。

ここまでは大丈夫です。
次のまとまりを見ます。

const observer = new IntersectionObserver(doWhenIntersect, options);

new でこれも IntersectionObserverのオブジェクト、observer というインスタンスを作っています。

ここでIntersectionObserverのオブジェクトって何?となりました。

IntersectionObserverはコンストラクター

調べていくと、「IntersectionObserverはコンストラクター」だそうです。

コンストラクター(関数)
new式を使用して新規オブジェクト(インスタンス)を作成する関数。

そういう関数があらかじめ用意されているようです。
これがいわゆる「API」ということでしょうか!

Web API の紹介 - ウェブ開発を学ぶ | MDN

クライアントサイドJavaScriptとはブラウザで動くJavaScriptのことですね。

そのうちのAPIは以下の2つに分けられるとあります。

  • ブラウザ API
     ┗ ブラウザに組み込まれているもの。 ←IntersectionObserverはこっち?
  • サードパーティ API
     ┗ デフォルトではブラウザに組込まれていない。Twitter APIなど

Web APIとしてこちらにもIntersectionObserverが載っていました。

Web API | MDN

Web API は通常 JavaScript とともに使用されますが、常にそうとは限りません。

意味深ですね。他の言語でもWeb API が使われることがあるみたいです。

IntersectionObserver

IntersectionObserver - Web API | MDN

Intersection Observer API - Web API | MDN

苦手意識のあるAPI、また少し分かってきた気がします。
ひとくくりにするにはその種類が多いですね。

IntersectionObserver() の中身を見てみると、doWhenIntersect がコールバック関数で、options が監視範囲の設定値です。

コールバック関数の中身はそのさらに下に書かれているので今は置いておいて...。

// それぞれのboxを監視する
boxes.forEach(box => {
  observer.observe(box);
});

監視対象の .box 要素をループ処理しています。

ここでforEachについて調べました。

JavaScriptでforEachメソッドを使う方法【初心者向け】 | TechAcademyマガジン

forEachはArrayオブジェクトに実装されたメソッド

配列もオブジェクトなのでメソッドがあります。書き方は下記です。

配列.forEach( コールバック関数による処理 )

ここでもコールバック関数!

box => { observer.observe(box); }

この部分、アロー関数で書かれていますが、

function(box) {
  observer.observe(box);
}

と同じです。box.box の各要素です。

observeメソッド

このはたらきは

IntersectionObserver が対象の要素を監視するよう命じます。

だそうです。

observer.observe(box); は、observerというオブジェクトに対してobserveメソッドをあてています。名前が一緒でややこしいですね。
引数に対象の要素を入れています。

つまりは「IntersectionObserver が各 .box 要素を監視する」ということですね。

/**
 * 交差したときに呼び出す関数
 * @param entries
 */
function doWhenIntersect(entries) {
  // 交差検知をしたもののなかで、isIntersectingがtrueのDOMを色を変える関数に渡す
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      activateIndex(entry.target);
    }
  });
}

ここで IntersectionObserver() のコールバック関数が出てきました。
そしてまたforEach。
なぜforEachを使っているのかは、参考本ページに書かれていました。

doWhenIntersect(entries) と引数を設けています。
ここには、doWhenIntersect が発動するタイミングで、監視している要素が入ってくるのだと思います。

コールバックは、入力引数として交差したすべての閾値を示すIntersectionObserverEntry オブジェクトの配列を、また参照としてIntersectionObserver オブジェクト自身を受け取ります。

ちょっと違いました^^;
IntersectionObserverEntry オブジェクトがよく分かりません。

閾値のリスト内のそれぞれの項目は、通過した閾値を説明するIntersectionObserverEntry オブジェクトです。つまり、それぞれの項目は指定された要素がルート要素とどれだけ交差したのか、要素が交差したと言えるのかどうか、推移が発生した方向を示します。

console.log(entries) とすると、閾値に指定している(今回はthreshold: 0)、交差のタイミングで表示されました。

分かったような気がします。大事なオブジェクト...!

isIntersectingプロパティ

監視対象が見えるようになったか、見えなくなったかを判断します。
entry.isIntersectingtrue なら見えるようになった
false なら見えなくなった

です。
(見えている状態が継続されている場合はtrueなのでしょうか)

要素が見え始めた場合に activateIndex という関数に entry.target という引数を渡しています。

targetプロパティ

先程の console.log(entries) のtargetプロパティを見てみると div.box とありました。

このあたりの細かい説明はあまり見つからず...「targetプロパティ」は広くいろいろなところで同じようにセレクタを値に持っているのかもしれませんね。

文章にすると「閾値の交差時に監視対象の要素が見えたら、次の関数を実行する」でしょうか。

/**
 * 目次の色を変える関数
 * @param element
 */
function activateIndex(element) {
  // すでにアクティブになっている目次を選択
  const currentActiveIndex = document.querySelector("#indexList .active");
  // すでにアクティブになっているものが0個の時(=null)以外は、activeクラスを除去
  if (currentActiveIndex !== null) {
    currentActiveIndex.classList.remove("active");
  }
  // 引数で渡されたDOMが飛び先のaタグを選択し、activeクラスを付与
  const newActiveIndex = document.querySelector(`a[href='#${element.id}']`);
  newActiveIndex.classList.add("active");
}

これは読めました。
目次に対して、先にactiveクラスを外してから、該当の要素にactiveクラスを付けています。

以上です!

Lozad.jsについて

こちらのプラグインの中でInterseprtion Observerが使われているということなので調べました。

IntersectionObserveの遅延読み込みライブラリのLozad.jsを紹介する - Qiita

使いやすそうですね。Interseprtion Observer を理解した上で使うならいいと思いました。

最後に

JavaScriptはまだまだ1文ずつ分解して読んでいかないと分からないところがあります。
謎のプロパティ、引数が出てくると心が折れそうになりますが、調べれば正体が分かったので、根気よく解読していこうと思います。

アロー関数とコールバック関数に慣れなくては。

***

2020年に始めたこのブログも何とか1年続けられました。奇跡!
後半息切れしましたが、2021年もマイペースに書いていきます!