Vue.jsでブラウザバック時にスクロール位置を元に戻す

Vue.js でブラウザバック時にスクロール位置を元に戻すJavaScript

Vue.jsを使って、Webサイトを作成していた際に、他の画面に遷移して元の画面に戻ってくると、スクロール位置がページトップに戻ってしまう…

そこで、ブラウザバック時にスクロール位置を元に戻す実装方法について。ライブラリ等は使わずに、実装していきます!

Vue.jsでブラウザバック時にスクロール位置を元に戻す

他ページ遷移前に、垂直方向にスクロールしたpx数をsessionStorageに保存

他ページに遷移する前に、垂直方向にスクロールしたpx数をwindow.scrollYで取得し、sessionStorageに保存します。

export default {
  mounted() {
    window.addEventListener("beforeunload", () => {
      sessionStorage.setItem("scrollY", window.scrollY);
    });
  }
}

beforeunloadは、ページ移動前に実施されるイベントです。例えば、外部リンクを開く前などに実施されます。

sessionStorageは、ページのセッションが続いている間、データを保存できる領域。似たようなものとして、localStorageがあります。

  • sessionStorage
    • タブやウィンドウを閉じると、データが破棄される
  • localStorage
    • データの保存期限がなく、タブやウィンドウを閉じてもデータが保持される

元のページに戻ってきた際に、指定の座標までスクロールさせる

戻るまたは進むアクションを行った後に、元のページに戻ってきた場合、指定の座標までスクロールさせます。

export default {
  mounted() {
    (() => {
      let entries = performance.getEntriesByType("navigation");
      entries.forEach((entry) => {
        if (entry.type == "back_forward" && sessionStorage.getItem("scrollY") != null) {
          window.scrollTo(0, sessionStorage.getItem("scrollY"));
        }
      });
    })();
    window.addEventListener("beforeunload", () => {
      sessionStorage.setItem("scrollY", window.scrollY);
    });
  }
}

戻るもしくは進むアクションを行なったかどうかをページ読み込み時に判断する際に、PerformanceNavigationTimingインターフェースを使います。

ナビゲーションの種類には、「navigate・reload・back_forward・prerender」がありますが、今回は、back_forwardを指定。

リファレンス:PerformanceNavigationTiming.type – Web APIs | MDN

戻るもしくは進むアクションを行い、scrollYキーがnullではない場合に、元の位置までスクロールさせています!

動的表示させている箇所がある場合、スクロール位置がずれてしまう

静的に表示させている箇所しかない場合は、上記に記載した方法で問題ありませんが、APIを叩いて情報を取得・表示させている箇所などは、その分スクロール位置がずれてしまいます。

そこで、DOMが更新された後に元の位置までスクロールさせるようにします。

export default {
  mounted() {
    this.getItem().then((response) => {
      // 取得したアイテムを変数に入れたりする何かしらの処理

      // DOM更新後に、スクロール位置を移動するようにする
      this.$nextTick(() => {
        let entries = performance.getEntriesByType("navigation");
        entries.forEach((entry) => {
          if (entry.type == "back_forward" && sessionStorage.getItem("scrollY") != null) {
            window.scrollTo(0, sessionStorage.getItem("scrollY"));
          }
        });
      });
    }).catch((e) => {
      console.log(e);
    });

    window.addEventListener("beforeunload", () => {
      sessionStorage.setItem("scrollY", window.scrollY);
    });
  },
  method: {
    async getItem() {
      // アイテムを取得する何かしらの処理
    }
  }
}

DOM更新完了後に、処理を実行する $nextTick()を使用しています。

callback を延期し、DOM の更新サイクル後に実行します。DOM 更新を待ち受けるために、いくつかのデータを変更した直後に使用してください。

API — Vue.js

これで、動的に表示させている箇所があった場合でも、ブラウザバックした際にスクロール位置がずれることはなくなりました!

safariで開いた場合に上述したやり方では上手くいかない

上述した実装方法で、問題なく元のスクロール位置に戻ると思ったのですが、safariでは、元のスクロール位置に戻りませんでした。

原因は、おそらくbfcacheなるものが効いているため、Chrome等のブラウザと挙動が変わってくるそう。

そこで、HistoryインターフェースのscrollRestorationプロパティを使って、スクロール位置を復元させる方法にしました。

mounted() {
  if ("scrollRestoration" in window.history) {
    window.addEventListener('load', () => {
      window.history.scrollRestoration = 'auto';
    });
  }
}

これだけでOK!

sessionStorageや$nextTick()を使わずとも、この記述だけで、ブラウザバックした際に、safariの場合でも、元のスクロール位置に戻るようになりました。

【補足】URLに#(ハッシュ)がついている場合

URLにハッシュがついている場合があるとします。例えば以下のような感じ。

https://hoge.com/#id

通常であれば、上記のURLにアクセスすると、ハッシュに指定したidの箇所にスクロールされますが、内部に動的に表示している箇所(APIから取得している箇所等)がある場合に、若干ずれてしまいます。

そこで、下記のようにscrollIntoView()を使用することにより、正常に動作するようになりました。

mounted() {
  if ("scrollRestoration" in window.history) {
    window.addEventListener('load', () => {
      const hash = this.$route.hash;
      if (hash) {
        document.getElementById(hash.replace("#", "").scrollIntoView();
      } else {
        window.history.scrollRestoration = 'auto';
      }
    });
  }
}

以上、最後まで読んでいただきありがとうございました!

Vue.js関連のおすすめ書籍

コメント

タイトルとURLをコピーしました