Intersection Observer に関するメモ。
Element.getBoundingClientRect()
ある HTML 要素がブラウザの表示されているエリア (ビューポート) に表示されているかどうかを確認してみたい場合があると思います。
まず最初に試してみるのは、scroll
イベントで Element.getBoundingClientRect()
で、イベントの発火毎に HTML 要素の位置を取得して、その値によって処理をする、というようなことをされるのではないでしょうか?
See the Pen
getBoundingClientRect by rough.and.cheap (@rough-and-cheap)
on CodePen.
ただ、この方法だと scroll
イベント毎に処理が発生するため、監視する要素数が増えるに従って、処理が重くなっていくということは想像に難くありません。
Intersection Observer API
そこで、色々と調べていくと、Intersection Observer API という API に行き着きます。
MDN によると、
ターゲットとなる要素が、ルート要素に交差するたびに呼び出せるコールバックを構成することができる、、
とあります。
なんだかとてもややこしい書き方ですが、端的に言うと、ブラウザ上の表示エリアや、スクロール可能な要素内で、ターゲット要素がどの程度見えているのか、という比率 ( 0.0 ~ 1.0 の間の値) に対して処理するコールバックを構成することができる、ということなんですね。
getBoundingClientRect() との違いは、
- スクロールの度に呼び出されるのとは違って、ビューポートに表示された段階でコールバックが呼び出される
- ピクセル単位で値を取得する代わりに、表示されている比率の値が返される
というところでしょうか。
Intersection Observer オブジェクトの生成
Intersection Observer を作成して使用するには、おおよそ以下のようになります。
var target = document.querySelector('#someBox');
let options = {
root: null,
rootMargin: '0px',
threshold: [0, 0.25, 0.5, 0.75, 1]
};
let observer = new IntersectionObserver(callback, options);
observer.observe(target);
ここで、IntersectionObserver のインスタンス生成時に渡す options
の内容によって、コールバックの呼び出される条件等を指定することができます。
root
- ビューポートを指定します。何も指定しなかった場合か、
null
の場合はデフォルトで、ブラウザのビューポートが使用されます。
ターゲットとなる要素はここで指定したルート要素の子要素である必要があります。 rootMargin
root
の周りのマージンです。"10px 20px 30px 40px"
のように CSS のmargin
プロパティと同じように指定できます。ターゲットがビューポートに見えているかどうかを判断するための範囲を拡大、縮小できます。デフォルトでは全て0
です。threshold
- ターゲットがビューポートにどの程度見えたときにコールバックを発火させるか、という閾値を、単一の数値、あるいは数値の配列で指定します。
例えば、[0, 0.5, 1]
とした場合、ターゲットがビューポートに 1ピクセルでも見えた時、ターゲットが半分見えた時、ターゲットが全て見えた場合の合計3回、コールバックが呼び出されます。
また、IntersectionObserver.observe()
に渡すターゲットは一つの要素 (Element
) で、複数の要素 (HTMLCollection
等) を渡すことはできません。
Intersection Observer で複数の要素を監視
複数の要素を監視するには、同じコールバックを使うのであれば、一つの IntersectionObserver のインスタンスを使用することができます。
単純に、それぞれの要素を observer.observ(target)
とすればよいだけです。
let elements = document.getElementsByClassName('someBox');
let options = {
root: null,
rootMargin: '0px',
threshold: [0, 0.25, 0.5, 0.75, 1]
};
// IntersectionObserver インスタンスの生成
let observer = new IntersectionObserver(callback, options);
for (let i = 0; i < elements.length; i++) {
// 同じインスタンスにターゲットとなる要素を渡す
observer.observe(elements[i]);
}
コールバックの実装
コールバックの実装については特筆する部分はありませんが、引数として entries
に observer.observ(target)
で追加したもの、observer 自体である observer
インスタンスが渡されてきますので、これらを利用して要素の操作等を行います。
function callback(entries, observer) {
// do anytihng
}
ターゲットの要素を取得するには、entries
をループで回してそこから取得する必要があります。
ただ、この entries
は IntersectionObserverEntry
の配列 なので、ターゲットとなっている要素を取得するには entry.target
として取得する必要があります。
function callback(entries, observer) {
entries.forEach((entry) => {
// observer.observe(target) した target を取得
let element = entry.target;
// do anything
});
}
また、肝心の交差率を取得するには、entry.intersectionRatio
として取得します。
function callback(entries, observer) {
entries.forEach((entry) => {
let element = entry.target;
// ターゲット要素が30%より多く見えた段階で処理を行う
if (entry.intersectionRatio > 0.3) {
// do anything
}
});
}
もしも、コールバック実行後に、以降のコールバック呼び出しを停止させたい場合は、observer.unobserve(target)
として、監視を停止させることができます。
function callback(entries, observer) {
entries.forEach((entry) => {
let element = entry.target;
if (entry.intersectionRatio < 0.3) {
// do anything
// 以降、このターゲットの監視を停止
observer.unobserve(entry.target);
}
});
}
MDN によると、コールバックはメインスレッドで実行されるので、可能な限り早く動作させる必要があります。
IntersectionObserver を使ったサンプル
上記までを踏まえた上で、簡単なサンプルを作成しました。
このサンプルは、MDN のサンプルプログラムをベースにして、複数のターゲットに対して、それぞれコールバックで交差比率を評価するときに、直前の交差比率も比較できるようにしています。
直前の交差比率と比較することで、どちらの方向にスクロールしているのかによっても処理を分けることができます。
See the Pen
YzqBMbd by rough.and.cheap (@rough-and-cheap)
on CodePen.
まとめ
IntersectionObserver を使うと、
- 要素の表示によってスタイルを変更させる
- 画像の LazyLoad
- ビューポートから外れた埋め込まれた動画の再生を停止する
等、色々応用がききそうです。
scroll
イベントで実装しているコールバックを、こちらに置き換えることも検討できるかもしれませんね。
関連
- Element.getBoundingClientRect() - Web API | MDN (日本語)
- Intersection Observer API - Web API | MDN (日本語)
0件のコメント