これは何
offsetTopを使ってコンポーネントの位置を取得して発火するイベントが、Safariでだけ挙動がおかしくなったので、その原因を探った時の記録です。
結論
本来offsetTopは最も近い祖先要素(offsetParent)からの距離を取得します。
The HTMLElement.offsetTop read-only property returns the distance from the outer border of the current element (including its margin) to the top padding edge of the offsetParent, the closest positioned ancestor element.
この時、最も近い祖先要素の選定方法がブラウザによって異なることが原因でした。
今回の場合、ChromeやFirefoxでは最も近い祖先要素がsection要素でしたが、Safariでは該当する要素がないためbody要素となっていました。
解決法
ひとまずの解決策として、ChromeやFirefoxで祖先要素になっているsectionにposition: relativeを指定しました。
これにより、Safariでも同じsectionが祖先要素となり、計算結果が一致するようになりました。
ただ、そもそもoffsetTopは以下の記事でも紹介されているように挙動が複雑なので、使用しないようにするのが良いと思います。
具体的に起こったこと
ページ内のコンポーネントの位置をoffsetTopを利用して取得し、コンポーネントの位置までスクロールしたらイベントを発火させる処理を作成していました。
const componentPosition =
componentWrapperRef.current?.offsetTop +
componentRef.current?.offsetTop
if (scrollY > componentPosition) {
hogehoge();
}
<main>
<section ref={componentWrapperRef}>
<div ref={componentRef}>
{コンポーネントの中身}
</div>
</section>
</main>
このとき、ChromeやFirefoxではcomponentRef.current?.offsetTopがdivからsectionまでの距離を返しますが、Safariではdivからmainまでの距離を返してしまいました。
結果として、Safariだけイベントが発火する位置がずれてしまっていました。