JavaScript
vue.js
Vuetify

Vuetifyのタブコンポーネントで各タブごとにスクロール位置を保持する

Vuetifyのタブコンポーネント

VuetifyはVue.jsに対応したUIコンポーネントライブラリです。今回はこの中のタブ(v-tabs)に関するTipsです。
https://vuetifyjs.com/ja/components/tabs

タブごとにスクロール位置を保持したい

例えば、3つのタブを作り、その中にInstagramのタイムラインのようなコンテンツのリストを表示するとします。
このとき、次のような事象が発生します。

  1. タブAでwindow.scrollYが500になるまでスクロール
  2. タブBに切り替え
  3. タブBでもスクロール位置(window.scrollY)が500になっている

感覚的には、タブAでwindow.scrollY=500の位置までスクロールしても、タブBに切り替えたときには一番上(window.scrollY=0)の位置を表示してほしいですよね。

方針

次のような方針で対応していきます。

  • スクロールのイベントハンドラを追加する
  • スクロールが発生したら、現在表示中のスクロール位置を保持する(タブごとに別々に保持)
  • v-tabsv-modelに指定したプロパティ(例: active)をwatchする
  • activeの変更を検知したタイミングで、保持しておいたスクロール位置に変更する

実装

Javascriptの処理はこんな感じ。

sample.vue
<script>
export default {
    data() {
        active: 'tabA', // アクティブなタブ名
        // 各タブのスクロール位置
        position: {
            tabA: null,
            tabB: null,
            tabC: null,
        },
    },
    mounted() {
        // イベントリスナの追加
        window.addEventListener('scroll', this.handleScroll);
    },
    destroyed() {
        // イベントリスナの削除
        window.removeEventListener('scroll', this.handleScroll);
    },
    methods: {
        handleScroll() {
            // 現在アクティブなタブのスクロール位置を保持
            this.position[this.active] = window.scrollY;
        }
    },
    watch: {
        // activeの変更を検知
        active() {
            // 切り替え後のタブですでに保持されたスクロール位置があればその位置を取得
            const y = this.position[this.active] || 0; 

            // 即時スクロールすると、切り替え前のタブの長さ < 切り替え後のタブの長さである場合に、切り替え前のタブの最大値までしかスクロールされないことがある(切り替え後のタブの内容が描画される前にスクロールしようとする)ので、setTimeoutでタイミングを少しずらす
            setTimeout(() => {
                window.scroll(0, y);
            }, 200);
        },
    },
};
</script>

終わりに

省略した<template>の部分は基本的にVuetifyのサンプルを見てもらえばと思いますが、要望がありましたら追記します。