IntersectionObserverで目次を同期させる実装と落とし穴
長い記事では、いま全体のどこを読んでいるかがわかると安心して読み進められる。本誌の記事ページには、読んでいる章を目次で示す機能がある。
scrollイベントを使わない理由
scrollイベントで見出しの位置を毎回計算すると、スクロールのたびにレイアウトの再計算が起き、中価格帯の端末では動きが引っかかる。IntersectionObserverを使えば、要素が画面に入ったときだけ通知を受け取れる。
判定領域を画面上部に絞る
const observer = new IntersectionObserver(onChange, { rootMargin: "0px 0px -70% 0px" }); rootMarginで下側を70%削ると、画面の上部30%に見出しが入ったときだけ「読んでいる」と判定できる。見出しが画面の下端に見えた瞬間に切り替わるのを防げる。
落とし穴:短い章
章が短いと、一度のスクロールで複数の見出しが判定領域を通過し、ハイライトが飛ぶ。本誌では、通知された見出しのうち最も下にあるものを採用し、画面上部より上に出た見出しを記録しておくことで対処した。
落とし穴:見出しのID
CMSから受け取った本文の見出しには、IDが付いていないことが多い。表示側で連番のIDを振ると、目次のリンクが機能する。日本語の見出しをそのままIDにするとURLが読みにくくなるので、本誌では「sec-1」のような連番にしている。
読了進捗との分担
画面上部の細い進捗バーは本文全体の位置を、目次は章単位の位置を示す。役割を分けたことで、どちらか一方を過剰に目立たせる必要がなくなった。