// A devlog by mood.

公開: 更新:

swup.js+Astro.js+Lenisのブラウザバックの位置ずれ

復元タイミングとスクロール制御を整理して、ブラウザバック時の位置ズレを解消したメモ。

swup.js+Astro.js+Lenisで、ブラウザバックするとスクロール位置復元がズレる問題にハマりました。
当時のpackage.jsonでは、@swup/scroll-pluginは^3.3.2、Astroは^5.12.9、Lenisは^1.3.8を指定していました。

起きたこと

記事一覧の途中から詳細ページへ移動し、ブラウザバックすると、元の位置に戻らずスクロール位置がズレました。
最初はページの高さだけが原因だと思いましたが、調べると次の2点を見直す必要がありました。

  • @swup/scroll-pluginとLenisの双方がスクロールを動かしていた
  • 戻り先のページの高さが落ち着く前に位置を復元していた

スクロール操作をLenisに一本化

当時はswup.scrollToを差し替え、プラグインからのスクロール操作をLenisへ渡しました。以下はその部分の抜粋です。

swup.scrollTo = (offset, animate = true) => {
  const target = Number.isFinite(offset) ? offset : 0;
  savedScrollPosition = target;
  lenis?.scrollTo(target, { immediate: !animate, force: true });
};

ページの高さが落ち着いてから復元する

スクロール先の位置が決まっていても、戻り先のページがまだ短いと、その位置まで移動できません。
そこで、requestAnimationFrameごとにscrollHeightを確認し、高さが3フレーム連続で変わらないか、800msが経過してから復元するようにしました。

await waitForStableScrollHeight({ stableFrames: 3, timeoutMs: 800 });
lenis?.scrollTo(targetPosition, { immediate: true, force: true });

この対応後、ブラウザバックで元の位置へ戻れるようになりました。高さの監視にはタイムアウトを設けているため、すべての画像や後から読み込む要素の高さが確定することまでは保証しません。

追記(2026年9月):この記事は2026年2月当時の対応記録です。後にScroll Plugin 4系への更新も試しました。その経緯は更新時にスクロール復元が崩れた話にまとめています。現在のリポジトリは3系を使用しており、復元処理もこの記事の抜粋より増えています。

scroll-pluginとLenisで挙動が違うの、冷静に見ればすぐ気づけそうなのに見事に沼りました。
こういうのあるあるですよね。

しっかりデバッグしていれば、もっと早く気づけたはずです。
高さは取得していたんですけど、Lenisの存在を忘れていたのが敗因です。

そもそもLenisのような慣性スクロールは、賛否が分かれるところですよね。
自分はMagic Mouseやトラックパッドに慣れているので好みですが、個人のポートフォリオならともかく、一般的なサイトでは基本的に必要ないですよね。
仮に採用する場合も、prefers-reduced-motionでは無効化するなど、別の選択肢を用意しておくのがよさそうですね。
WebGL中心や演出重視であれば「体験を作る目的」で導入するのはアリかなと思いますが。

以上、おわり。