開発メモ
Vueリストの自動スクロール実装 - scrollIntoViewのblock: centerで先読み表示
結論
リストで矢印キー移動時に選択アイテムを自動スクロールするにはscrollIntoView({ block: 'center' })を使う。block: 'nearest'だと画面端まで来ないとスクロールしないため、先のアイテムが見えずUXが悪い。
watch(() => props.currentIndex, (newIndex) => {
if (newIndex < 0 || !listRef.value) return
nextTick(() => {
const item = listRef.value?.querySelector(`[data-index="${newIndex}"]`)
if (item) {
item.scrollIntoView({ block: 'center', behavior: 'smooth' })
}
})
})
問題
ファイルリストで矢印キーを使って移動する際に、選択アイテムが画面外に出ても自動でスクロールしない問題があった。

具体的には:
- 38件のアイテムがあるリストで、20番目あたりを選択中
- 下矢印キーで移動しても、38番目付近まで来ないとスクロールが発生しない
- 先に何件あるのかがわからず、UXが悪い
最初の実装: block: 'nearest'
最初はscrollIntoView({ block: 'nearest' })で実装した。
item.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
この実装の問題点:
- アイテムが表示範囲内にあればスクロールしない
- 画面の端ギリギリまで来て初めてスクロールが発生
- 先のアイテムが見えないため、リストの終わりが予測できない
改善: block: 'center'
block: 'center'に変更すると、選択アイテムが常にリストの中央付近に表示される。
item.scrollIntoView({ block: 'center', behavior: 'smooth' })
メリット:
- 選択アイテムの上下に同程度のアイテムが見える
- 先に何件あるかが常に把握できる
- リストの最初/最後付近では自然な位置に収まる
実装の全体像
テンプレート
<div ref="listRef" class="sidebar-list">
<div
v-for="(item, index) in items"
:key="item.file_name"
:data-index="index"
class="sidebar-item"
:class="{ active: index === currentIndex }"
@click="emit('select', index)"
>
<!-- アイテムの内容 -->
</div>
</div>
ポイント:
- リストコンテナに
ref="listRef"を設定 - 各アイテムに
:data-index="index"を追加してDOM検索に使用
スクリプト
const listRef = ref<HTMLElement | null>(null)
watch(() => props.currentIndex, (newIndex) => {
if (newIndex < 0 || !listRef.value) return
nextTick(() => {
const item = listRef.value?.querySelector(
`[data-index="${newIndex}"]`
) as HTMLElement | null
if (item) {
item.scrollIntoView({ block: 'center', behavior: 'smooth' })
}
})
})
ポイント:
nextTick()でDOM更新後に実行data-index属性でアイテムを特定behavior: 'smooth'でスムーズなスクロール
Afterの動作例:

scrollIntoViewのblockオプション比較
| オプション | 挙動 | ユースケース |
|---|---|---|
'start' | 要素をコンテナの上端に配置 | ページトップへの移動 |
'center' | 要素をコンテナの中央に配置 | リストナビゲーション |
'end' | 要素をコンテナの下端に配置 | チャットの新着メッセージ |
'nearest' | 最小限のスクロールで表示 | 既に見えている可能性が高い場合 |
まとめ
リストのキーボードナビゲーションではscrollIntoView({ block: 'center' })を使うと選択アイテムの前後が常に見えてUXが良い。block: 'nearest'は画面端まで来ないとスクロールしないため、先読みができず使いづらい。