@swup/scroll-plugin更新でスクロール復元が崩れた話
3.x系から4.0系に更新後にブラウザバック時のスクロール位置復元が崩れたので見直したメモ。
@swup/scroll-pluginをアップデートしたら、ブラウザバック時のスクロール位置復元が壊れました。
記事一覧から詳細に移動して、戻る。
本来なら一覧の元いた位置に戻ってほしいのに、ページ上部に戻るようになってしまった。
SPAっぽいページ遷移を入れているサイトではよくあるやつですが、今回は@swup/scroll-pluginを4.0系へ更新したタイミングで表面化しました。
使っている構成
このテンプレートでは、ページ遷移とスクロールまわりに以下を使っています。
swup@swup/scroll-plugin@swup/js-pluginlenisgsap
Swupでページ遷移を差し替えつつ、Lenisでスムーススクロールを管理しています。
さらにGSAPで遷移アニメーションを入れているので、普通のブラウザ遷移よりスクロール復元のタイミングが少しシビアです。
今回の更新では、@swup/scroll-pluginを3.3系から4.0系へ上げました。
そこで、以前の「swup.scrollToを直接上書きする」やり方を見直すことになりました。
特にブラウザバックでは、
- 遷移前のスクロール位置を保存する
- 戻り先のHTMLを差し替える
- 戻り先の高さが決まる
- 保存していた位置へ戻す
- 遷移アニメーションを終える
という順番が崩れると、元の位置に戻れません。
壊れ方
記事一覧の下のほうまでスクロールして、記事詳細へ移動する。
そこからブラウザバックすると、本来は一覧の同じ位置に戻ってほしい。
でも@swup/scroll-plugin更新後は、復元位置が復元しなくなった。
原因としては、Swup Scroll Plugin側のスクロール復元とLenis側のスクロール状態がうまく噛み合っていなかったこと。
Swup Scroll Pluginは戻り先のスクロール位置を知っていますが、実際のスクロールはLenisが握っている。
ここがずれると、Swupは「復元したつもり」でも、Lenis上では別の位置にいる状態になります。
以前はswup.scrollToを上書きして、そこでLenisのscrollToを呼んでいました。
swup.scrollTo = (offset, animate = true) => {
const target = Number.isFinite(offset) ? offset : 0;
if (!lenis) {
window.scrollTo(0, target);
return;
}
lenis.resize();
lenis.scrollTo(target, { immediate: !animate, force: true });
};
一旦はこれで動いていたんですが、プラグイン内部の流れに後から差し込む形なので、@swup/scroll-pluginの更新に弱い。
スクロール開始・終了のフックや、横方向の位置、スクロールコンテナの扱いも自前で合わせる必要がありました。
対処
最終的には、@swup/scroll-plugin v4の流れに合わせて、swup.scrollToを上書きするのをやめました。
代わりに、@swup/scroll-pluginのscrollFunctionを使って、SwupがスクロールしたいタイミングだけLenisへ橋渡しする形に変更しました。
const scrollWithLenis =
(lenis: Lenis | null) =>
(scrollContainer, top, left, animate, start, end) => {
start();
if (!lenis) {
window.scrollTo(left, top);
end();
return;
}
lenis.resize();
if (!animate) {
lenis.scrollTo(top, { immediate: true, force: true });
end();
return;
}
lenis.scrollTo(top, {
force: true,
onComplete: end,
});
};
設定側ではこんな感じです。
new SwupScrollPlugin({
animateScroll: {
betweenPages: false,
samePageWithHash: true,
samePage: true,
},
offset: 112,
scrollFunction: scrollWithLenis(lenis),
});
これで、スクロール位置の保存や復元の判断はSwup Scroll Pluginに任せつつ、実際にスクロールを動かす部分だけLenisに渡せます。
startとendもプラグインから渡されるので、スクロール処理の開始・完了をSwup側に返せるのもよいところです。
自前でscroll:startやscroll:endを呼ぶより、役割がかなり分かりやすくなりました。
まとめ
今回の修正でよかったのは、独自の上書きを減らせたことです。
swup.scrollToを直接差し替えると、最初は手早く直せます。
でも、Swup Scroll Pluginが本来持っている「いつリセットするか」「いつ復元するか」「スクロールコンテナをどう扱うか」まで自分で面倒を見ることになります。
今回はscrollFunctionに寄せたことで、
- スクロール位置の保存・復元はプラグインに任せる
- 実際のスクロール実行だけLenisに任せる
- 通常遷移では先頭へ戻す
- ブラウザバックでは元の位置へ戻す
という分担に戻せました。
ライブラリのアップデートで壊れたときは、つい動いていた頃の処理を足して直したくなります。
でも、ライブラリ側に用意されている拡張点を使ったほうが、次の更新に強くなりやすいですね。
以上、おわり。