これは何
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だけイベントが発火する位置がずれてしまっていました。