tanstack-vue-virtual-skilld について
大規模または動的なコレクションを扱う Vue 開発者向けの、ヘッドレスなリスト・グリッド仮想化アダプターです。Vue のリアクティビティを virtual-core の Virtualizer に接続し、ウィンドウスクロールや末尾固定のフィードにも対応します。
主な機能
- 01リアクティブな useVirtualizer と useWindowVirtualizer
- 02リストとグリッドの仮想化
- 03動的な項目サイズの計測
- 04末尾固定フィードとスクロール復元
活用シーン
- 01大規模リストの DOM 項目数削減
- 02末尾に固定されるチャットフィードの構築
- 03画面遷移後のスクロール位置復元
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ skilld-dev/ vue-ecosystem-skills --skill tanstack-vue-virtual-skilldインストール後、Claude Code で/tanstack-vue-virtual-skilldを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/chat-end-anchoring.mdpatterns.mdvirtualizer-api.mdvue-adapter.md詳細
@tanstack/vue-virtual
Vue 向けのヘッドレスなリスト/グリッドの仮想化機能です。アダプターは薄いレイヤーで、useVirtualizer と useWindowVirtualizer はコアの Virtualizer を構築して Vue のリアクティビティに接続し、Ref として返します。以下のすべてのオプションとメソッドは @tanstack/virtual-core に由来し、アダプターから再エクスポートされています(src/index.ts:21)。
バージョン情報
- 対象:
@tanstack/[email protected](package.json:3)。 - 固定依存関係:
@tanstack/[email protected](package.json:52)。 - ピア依存関係:
vue ^2.7.0 || ^3.0.0(package.json:59)。 - アダプター API は 3.13.x を通じて安定しています。リリース 3.13.25 から 3.13.39 では virtual-core のバージョンのみが更新されています(vue-virtual の変更履歴)。
- インストール:
npm install @tanstack/vue-virtual(インストールガイド)。
クイックスタート:高さが可変の行
公式の Vue の動的な例に沿った標準的なパターンです。ブロックの変換によって、描画されたブロックは最初の項目の start に配置され、各項目はブロック内で通常どおりに配置されます。
<script setup lang="ts">
import { computed, onMounted, onUpdated, ref, shallowRef } from 'vue'
import { useVirtualizer } from '@tanstack/vue-virtual'
const parentRef = ref<HTMLElement | null>(null)
const rows = Array.from({ length: 10000 }, (_, i) => `Row ${i}`)
const rowVirtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => parentRef.value,
estimateSize: () => 55,
overscan: 5,
})
const virtualRows = computed(() => rowVirtualizer.value.getVirtualItems())
const totalSize = computed(() => rowVirtualizer.value.getTotalSize())
const itemEls = shallowRef<HTMLElement[]>([])
function measureAll() {
rowVirtualizer.value.measureElement(null) // prune disconnected nodes
itemEls.value.forEach((el) => el && rowVirtualizer.value.measureElement(el))
}
onMounted(measureAll)
onUpdated(measureAll)
</script>
<template>
<div ref="parentRef" style="height: 400px; overflow-y: auto; contain: strict">
<div :style="{ height: `${totalSize}px`, position: 'relative', width: '100%' }">
<div
:style="{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualRows[0]?.start ?? 0}px)`,
}"
>
<div
v-for="virtualRow in virtualRows"
:key="virtualRow.key"
:data-index="virtualRow.index"
ref="itemEls"
>
{{ rows[virtualRow.index] }}
</div>
</div>
</div>
</div>
</template>
スクロールコンテナーには、上限のある高さと overflow: auto が必要です。内側のサイズ調整用 div には getTotalSize() を設定します。
ウィンドウバーチャライザー
useWindowVirtualizer はブラウザーのウィンドウに合わせてスクロールします。getScrollElement(SSR セーフ)と initialOffset はあらかじめ設定されるため、どちらも渡さないでください(vue-virtual のドキュメント、src/index.ts:104-114)。リストがほかのコンテンツの下にある場合は、そのオフセットを scrollMargin に計測します。
const parentOffset = ref(0)
onMounted(() => {
parentOffset.value = parentRef.value?.offsetTop ?? 0
})
const rowVirtualizer = useWindowVirtualizer(
computed(() => ({
count: rows.length,
estimateSize: () => 45,
scrollMargin: parentOffset.value,
})),
)
パターンの出典:公式のウィンドウの例。
Vue アダプターのルール(src/index.ts で確認済み)
Virtualizerの生のインスタンスではなく、Ref<Virtualizer>を返します。スクリプトでは.valueを使用し、テンプレートでは自動的にアンラップされます(src/index.ts:30,69)。- オプションは
MaybeRefを受け付けます。通常のオブジェクトは一度だけ適用されます。count、overscan、enabledなどをリアクティブに更新するには、オプション全体をrefまたはcomputedにして渡してください。アダプターは() => unref(options)を監視し、変更時にsetOptionsを呼び出します(src/index.ts:48-65)。 getScrollElement()はimmediate: trueで監視されます。セットアップ時にまだnullのテンプレート ref も、マウント時に検出されます(src/index.ts:36-46)。onChangeはラップされています。アダプターはユーザーのコールバックより先に、内部のshallowRefでtriggerRefを呼び出します。そのため、computed(() => rowVirtualizer.value.getVirtualItems())はスクロールやサイズ変更時に再評価されます(src/index.ts:53-56)。- オブザーバーは
onScopeDisposeでクリーンアップされます(src/index.ts:67)。 measureElementで計測する項目には、デフォルトでdata-indexのインデックス属性が必要です(indexAttributeで変更できます)。- アダプターがあらかじめ設定するため、
observeElementRect、observeElementOffset、scrollToFnは渡さないでください(src/index.ts:83-90)。
詳細:Vue アダプター API。
API リファレンス
オプションの完全な表、インスタンスメソッド、VirtualItem の形式:Virtualizer API。
useVirtualizer の必須オプション:count、getScrollElement、estimateSize(virtualizer のドキュメント)。
virtual-core 3.14.0 以降の新機能
vue-virtual 3.13.39 には core 3.17.11 が含まれています。以下は core のマイナーバージョンで追加された機能です(virtual-core の変更履歴)。
- 3.16.0:チャット、ログ、逆方向フィード向けの末尾アンカー モード。
anchorTo: 'end'、followOnAppend、scrollEndThresholdに加え、scrollToEnd()、isAtEnd()、getDistanceFromEnd()が追加されました。チャットと末尾アンカーを参照してください。 - 3.15.0:
initialMeasurementsCacheとinitialOffsetを使ったスクロール復元の往復に対応するtakeSnapshot()。 - 3.15.0:後方スクロール中はデフォルトでスクロール調整による補正をスキップするようになりました(「上にスクロールすると項目が跳ねる」問題を修正)。上書きするには、オプションではないインスタンスプロパティ
shouldAdjustScrollPositionOnItemSizeChangeを割り当てます。 - 3.15.0:iOS Safari のモーメンタムスクロール処理がデフォルトで有効になりました。タッチ中またはバウンス中は、スクロール補正の書き込みが遅延されます。
- 3.17.0:
useCachedMeasurementsにより、リストが非表示でも計測値を保持します。 - 3.15.0 より前から利用できるオプション:
laneAssignmentModeを指定したlanes(3.14.0)、gap、indexAttribute、initialMeasurementsCache、isRtl。
ベストプラクティス
- 動的な項目では大きめに見積もります。初期位置とスクロールバーを安定させるため、
estimateSizeは想定される最大サイズに近い値に設定します(virtualizer のドキュメント)。 - 項目ごとの絶対位置指定よりも、ブロック単位の変換(クイックスタートの例を参照)を優先します。スムーズな
scrollToIndexは、対象から遠い項目の計測をスキップします。ブロック単位の変換を使うと、描画ブロック内の整合性を保てます(virtualizer のドキュメント)。 - データの並べ替え、フィルタリング、先頭への項目追加、ストリーミングを行う場合は、安定した
getItemKey(インデックスではなく行 ID)を指定します。インデックスをキーにすると、末尾アンカー付きチャットとスクロール復元で問題が発生します(チャットのドキュメント)。 - 共通のスクロールコンテナー内で項目を絶対配置する場合は、項目の開始位置から
scrollMarginを差し引きます(virtualizer のドキュメント)。 - 間隔を
getTotalSize()に反映させるには、CSS のマージンではなくgapオプションを使います。 countの変更時には計測キャッシュが無効化され、getTotalSize()が自動的に更新されます(3.13.13 で修正、変更履歴)。Vue では、リアクティブなデータを引き続きcomputedオプション経由で渡してください。- 効果があると確認されていない限り、
useAnimationFrameWithResizeObserverはオフのままにします。有効にすると計測が 1 フレーム遅延されます(virtualizer のドキュメント)。 - 計測値を保持するには、オブザーバーをアンマウントするのではなく、
enabled: falseで一時停止します。
masonry、パディング、固定ヘッダー、スクロール復元、非表示リストのレシピ:パターン。
v2 からの移行:useVirtual はなくなりました。v3 では useVirtualizer、size ではなく count オプション、parentRef ではなく getScrollElement、および data-index を持つ measureElement の ref を使います(vue-virtual のドキュメント)。
参考資料
- Vue アダプター API:シグネチャ、リアクティビティの契約、SSR の動作。
- Virtualizer API:すべてのオプション、デフォルト値、インスタンスメソッド、
VirtualItem。 - パターン:masonry、スクロールマージン、固定範囲、スクロール復元、非表示リスト。
- チャットと末尾アンカー:Vue での
anchorTo: 'end'を使ったチャットフィード。