😽
【Vue.js】 v-data-table スクロール位置を調整する方法
概要
Vuetifyのv-data-tableを使って大量のデータを表示する場合、ページネーションとスクロールの併用はよくあるパターンです。
ただしそのままだと、ページを切り替えてもテーブルのスクロール位置が維持されたままになり、UXが悪化することがあります。
本記事では、ページネーションのたびにスクロール位置を先頭にリセットする方法をご紹介します。
解決方法(watch + nextTick + DOM操作)
v-data-tableは内部的にdiv.v-table__wrapper
というスクロールコンテナを生成します。
これをJavaScriptで取得し、ページ変更時にscrollToで先頭に戻します。
DOM要素への参照用の変数定義
テンプレートにある要素をJavaScript側から参照するための変数を定義します。
const tableContainerRef = ref(null)
templateでは、v-data-tableをdivタグで囲い、DOM要素を取得できるようにする。
<div ref="tableContainerRef">
<v-data-table
:items="paginatedItems"
:headers="headers"
:items-per-page="itemsPerPage"
:page="currentPage"
hide-default-footer
fixed-header="true"
height="500"
/>
</div>
watchの中で、DOM操作し、スクロール位置を調整
watch(currentPage)
によってページ番号の変更を検知し、次にnextTick()
を使ってDOMの更新が完了するのを待ちます。
その後、Vuetifyが内部的に生成する.v-table__wrapper
要素をdocument.querySelector
で取得し、scrollTo({ top: 0 })を使ってスクロール位置をテーブルの先頭に戻します。
watch(currentPage, async () => {
await nextTick()
const tableWrapper = document.querySelector('.v-table__wrapper')
tableWrapper?.scrollTo({
top: 0,
behavior: 'auto',
})
})
サンプルコード
<script setup>
import { ref, computed, watch, nextTick } from 'vue'
const itemsPerPage = ref(100)
const currentPage = ref(1)
const items = Array.from({ length: 1000 }, (_, i) => ({
id: i + 1,
name: `ユーザー${i + 1}`,
email: `user${i + 1}@example.com`,
}))
const headers = [
{ title: 'ID', key: 'id' },
{ title: '名前', key: 'name' },
{ title: 'メール', key: 'email' },
]
const pageCount = computed(() => Math.ceil(items.length / itemsPerPage.value))
const paginatedItems = computed(() => {
const start = (currentPage.value - 1) * itemsPerPage.value
return items.slice(start, start + itemsPerPage.value)
})
// スクロール用のrefを追加
const tableContainerRef = ref(null)
watch(currentPage, async () => {
await nextTick()
// Vuetifyが自動で作るスクロールエリアを取得してスクロール
const tableWrapper = document.querySelector('.v-table__wrapper')
tableWrapper?.scrollTo({
top: 0,
behavior: 'auto',
})
})
</script>
<template>
<v-app>
<v-main>
<v-card>
<v-card-item>
<v-row>
<v-col>
<!-- ref をつける -->
<div ref="tableContainerRef">
<v-data-table
:items="paginatedItems"
:headers="headers"
:items-per-page="itemsPerPage"
:page="currentPage"
hide-default-footer
fixed-header="true"
height="500"
/>
</div>
<div class="d-flex justify-center mt-4">
<v-pagination v-model="currentPage" :length="pageCount" :total-visible="5" />
</div>
</v-col>
</v-row>
</v-card-item>
</v-card>
</v-main>
</v-app>
</template>
<style>
.custom-data-table th,
span.custom-header {
white-space: nowrap;
width: auto;
max-width: max-content;
overflow: hidden;
text-overflow: ellipsis;
}
.custom-data-table {
max-height: 600px;
overflow-x: auto;
}
.custom-data-table thread {
position: sticky;
top: 0;
z-index: 10;
background-color: rgb(216, 94, 94);
}
</style>
Discussion