🧷
Vuetify3 VDataTableで複数列を固定する方法
Vuetify3にあるVDataTableの列をExcelの列固定機能のように複数の列を固定しようとした際、手間取ったので備忘録としてまとめます。
やりたかったこと
- Vuetify3にあるVDataTableの列をExcelの列固定機能のように左から1列目と2列目を固定し、それ以降はスクロール可能にしたかった
解決策
-
headers
のパラメータのうち、固定したい列に対して以下の設定を行う-
fixed
の値をtrue
に設定する -
width
の値を設定する
-
何に躓いたのか
当初、VDataTableドキュメントを見て「 fixed
パラメータを適用することで固定化できるな」と理解していた。
実際適用してみると、1列目だけ固定したい場合はうまくいったが、左から2列分を固定しようとすると、2列目が1列目を覆い隠してしまうという動作となってしまった
設定 | 期待する動作 | 実際の動作 |
---|---|---|
fixed: true(1列目のみ) | 1列目が固定される | ✅ 期待通り |
fixed: true(1列目と2列目) | 1, 2列目が固定される | ❌ 2列目が1列目を覆う |
- やりたかった動作
- 実際の動作
つまずいた際のコード
<script lang="ts" setup>
import { ref } from "vue";
const headers = ref([
{ title: "Name", key: "name", fixed: true },
{ title: "Manufacturer", key: "manufacturer", fixed: true },
{ title: "Year", key: "year" },
{ title: "Sales", key: "sales" },
{ title: "Exclusive", key: "exclusive" },
]);
const consoles = ref([
{
name: "PlayStation 5",
manufacturer: "Sony",
year: 2020,
sales: "10M",
exclusive: true,
},
{
name: "Xbox Series X",
manufacturer: "Microsoft",
year: 2020,
sales: "6.5M",
},
{
name: "Nintendo Switch",
manufacturer: "Nintendo",
year: 2017,
sales: "89M",
exclusive: true,
},
{
name: "PlayStation 4",
manufacturer: "Sony",
year: 2013,
sales: "116M",
exclusive: true,
},
{
name: "Xbox One",
manufacturer: "Microsoft",
year: 2013,
sales: "50M",
exclusive: false,
},
{
name: "Nintendo Wii",
manufacturer: "Nintendo",
year: 2006,
sales: "101M",
exclusive: true,
},
]);
</script>
<template>
<v-data-table :headers="headers" :items="consoles" hide-default-footer>
<template #item.exclusive="{ item }">
<v-checkbox-btn v-model="item.exclusive" :ripple="false" />
</template>
</v-data-table>
</template>
どのように解決したか
VDataTableHeaderの実装を確認した
こちらを確認すると fixed: true
の場合には以下のCSSが指定されていることがわかる
function getFixedStyles (column: InternalDataTableHeader, y: number): CSSProperties | undefined {
if (!(props.sticky || props.fixedHeader) && !column.fixed) return undefined
return {
position: 'sticky',
left: column.fixed ? convertToUnit(column.fixedOffset) : undefined,
top: (props.sticky || props.fixedHeader) ? `calc(var(--v-table-header-height) * ${y})` : undefined,
}
}
ここから、以下のことがわかった
-
position: 'sticky'
が指定されているため、一番右の列はスクロールにて端に移動しても常に表示される - 2列目以降を固定するために、
column.fixedOffset
の値を使用している
現状の動作を見ると、sticky
は意図した通りに動作しているように見えるが、 left
の値が適切に設定されていないようにみえる
では fixedOffset
はどのように設定されているのか
column
の値は useHeaders
から返されていたため、そちらのコードを確認した
function setFixedOffset (item: InternalDataTableHeader, fixedOffset = 0) {
if (!item) return fixedOffset
if (item.children) {
item.fixedOffset = fixedOffset
for (const child of item.children) {
fixedOffset = setFixedOffset(child, fixedOffset)
}
} else if (item.fixed) {
item.fixedOffset = fixedOffset
fixedOffset += parseFloat(item.width || '0') || 0
}
return fixedOffset
}
let fixedOffset = 0
for (const item of items) {
fixedOffset = setFixedOffset(item, fixedOffset)
}
}
確認すると、上記のコードにて指定されていることがわかった。
こちらでは、左から順に width
を足していき、fixedOffset
としていた
そのため、各列に width
の値が必要であることがわかる
fixedとwidthを指定して解決
fixed:true
を指定した列には width
を指定することで、2列目以降もExcelのように列を固定することができた
最後に
- 今回はVuetifyの機能でVDataTableで複数の列を固定化する方法を調査した
- 本来であればCSSの仕様から
left
を指定しないと実現できないよねということを解説したかったが、うまく説明できなかったので今回は割愛
Discussion