Intersection ObserverはLazyloadなどでよく使われるJavascriptの組み込み機能す。処理の重いscrollイベントは嫌われ、ScrollイベントをIntersection Observerで置き換えてjavascript記述量や処理軽減するのが最近のトレンドになっています。
サイトのサイドバーにある目次(Table of Contents)ウィジェットで現在の見出しをハイライトする際にIntersection Observerを使えないか?と思ってトライしました。思いもしないトラブルにあったのでメモしておきたいと思います。
Intersection Observerで考査を検出しないことがある
見出し要素をIntersection Observerで交差検出します。その際に、rootmarginを小さく(ビューポートを狭くする)し、かつ、スクロールが高速な場合に、交差検出しない(取りこぼす)トラブルが発生しました。
Intersection Observerのオプションは以下のような設定にしてありました。画面の中央に横線を配置し、それにタッチしたら検出するようなイメージです。
- rootMargin:-50%,0
- threshold:0
自分の環境を怪しんで色々試したのですが、原因が全く分からず、半ばあきらめていたのですが、真面目に検索してみると、同様なトラブル事例がありました。
Intersection Observerは、監視対象のDOM要素の位置をチェックする非同期関数をループで実行します。これはブラウザのレンダリングサイクルと組み合わされており、非常に高速に実行されますが(ほとんどのデバイスでは、60 fps、または16.66ミリ秒ごとに1回)、これらのチェックよりも速くスクロールバーを移動すると、IOAPIが一部の可視性の変化を検出しない場合があります。実際、検出されていない要素はレンダリングされていません。
https://stackoverflow.com/questions/61951380/intersection-observer-fails-sometimes-when-i-scroll-fast
スクロールが高速な場合は、Intersection Observerは万能ではないようです。
検出ウィンドウが極小の場合、検出要素が高速で移動していると、検出ウィンドウを通り抜けてから描画されてしまうことが起こり得ます。
色んな条件で試した感じでは、
- 検出要素に対してビューポートが十分に大きくないと、取りこぼしが発生しやすい
ようです。
私の対策
Intersection Observerのオプションを初期状態からいじらない。
見出し要素はそこそこ小さいので、rootmarginをいじらないか、なるべく狭くしないようにすると取りこぼしが無くなりました。
他の対策:検出要素を大きくする
他のサイトの記事を見ても、見出し要素を検出するのではなく、見出しを含むセクション全体を検出するようにして、検出要素をなるべく大きくしていました。HTML全体を検出要素で綺麗に埋めてしまうイメージになります。HTMLを自動修正してこの方法を適用する場合、見出しの付け方のルールが守られている前提が必要です。ちょっと使いにくい感じがします。
その他の対策
外部サイトで、解決方法がいくつか紹介されています。
IntersectionObserverの仕様の制限を回避するのではなく、IntersectionObserverを使わない解決方法だったので、私の場合は参考になりませんでした。

上方向のスクロールの検出が難しい
lazyloadのように一度表示したら終わりという処理ならいいのですが、見出しのハイライトではスクロール方向に追従して現在の見出しをハイライトする必要があります。
IntersectionObserverは、見出しがビューポートに入った時に検出されます。rootMarginが0の場合は検出要素が画面に表示されたタイミングで検出となります。
下にスクロールしていくときは問題なくても、上にスクロールしていくときには中々見出しが出てこないので現在表示しているセクションと見出しのハイライトの対応が取れなくなります。
逆方向のスクロールで思った動きにならない理由は、見出しと次の見出しまでの検出要素が無い期間があるためです。この記事の前半で紹介したように、記事全体を検出要素で埋めるようにして、検出要素がない領域を無くせばこのような問題は起きません。しかし、見出しだけを検出要素にしている場合は、逆スクロール時に挙動がおかしくなります。
以下の記事で、その対処方法が書かれていて、その通りにやると逆方向のスクロールで手前の見出しの領域に入ると検出してくれるようになります。
まとめ
IntersectionObserverは積極的に使っていきたい技術ですが、マウススクロールが高速な場合に描画されない要素は検出できないことがあります。
初期状態のオプションで使用すれば問題は起きませんが、rootmarginを1pxとかの高さにすると交差検出漏れが起きます。
また、見出しハイライトのようなものに適用する場合、逆スクロール時には手前の検出要素がなかなか出てこないのでハイライトがずれたように見えます。
その場合は、特殊な方法で対応する必要がありますので、IntersectionObserverならコーディングが簡単になるというメリットも薄れてしまいます。何でもかんでもIntersectionObserverというのも考え物だと思いました。