n-virtual-list

Virtual list component for efficient rendering of large lists. Invoke when user needs to implement high-performance lists with thousands of items in Naive UI.

SKILL.md

GitHub

Naive UI Virtual List Component

When it comes to virtual lists, it can feel like a list is infinitely long, but in reality it just secretly hides the invisible elements. Perfect for rendering large datasets efficiently.

When to Use

Use this component when:

  • Large lists: Render lists with thousands of items efficiently
  • Chat messages: Display large message histories
  • Data tables: Show large datasets without performance issues
  • Infinite scroll: Implement virtual scrolling for better performance

When to Invoke

Invoke this skill when:

  • User needs to render large lists efficiently
  • User wants to implement virtual scrolling
  • User needs dynamic item sizes
  • User wants to scroll to specific items programmatically
  • User asks about performance optimization for large lists

Features

  • Virtual Rendering: Only renders visible items for performance
  • Fixed or Dynamic Size: Support for fixed or dynamic item heights
  • Programmatic Scroll: Scroll to index, key, or position
  • Keep-alive Support: Preserve scroll state with keep-alive
  • Resizable Items: Dynamic item size calculation
  • Scrollbar Integration: Built-in scrollbar support

API Reference

Virtual List Props

Name Type Default Description
default-scroll-key string | number undefined Default scroll key.
default-scroll-index number undefined Default scroll index.
ignore-item-resize boolean false Ignore item resize for slightly better performance.
items Array<object> [] Data to display.
item-resizable boolean false Whether dynamic sizing is enabled.
item-size number required Minimum height of the item in pixels.
items-style string | CSSProperties undefined Items container style.
key-field string 'key' Field name of option key.
padding-top string | number undefined Distance from the top.
padding-bottom string | number undefined Distance from the bottom.
scrollbar-props ScrollbarProps undefined Scrollbar props.
visible-items-tag string 'div' Items container tag.
visible-items-props object undefined Items container props.
on-scroll (event: Event) => void undefined Scrolling callback function.
on-wheel (event: WheelEvent) => void undefined Callback for wheel event.
on-resize (event: ResizeObserverEntry) => void undefined Element resizing callback.

Virtual List Methods

Name Type Description
scrollTo ScrollTo Scroll to a position.

ScrollTo Type

interface ScrollTo {
  (x: number, y: number): void
  (options: {
    left?: number
    top?: number
    behavior?: ScrollBehavior
    debounce?: boolean
  }): void
  (options: {
    index: number
    behavior?: ScrollBehavior
    debounce?: boolean
  }): void
  (options: {
    key: string | number
    behavior?: ScrollBehavior
    debounce?: boolean
  }): void
  (options: {
    position: 'top' | 'bottom'
    behavior?: ScrollBehavior
    debounce?: boolean
  }): void
}

Basic Usage

Fixed Size Items

<template>
  <n-virtual-list style="max-height: 240px" :item-size="42" :items="items">
    <template #default="{ item }">
      <div :key="item.key" class="item" style="height: 42px">
        <img class="avatar" :src="item.avatar" alt="">
        <span>{{ item.value }}</span>
      </div>
    </template>
  </n-virtual-list>
</template>

<script setup>
import { ref } from 'vue'

const items = ref(
  Array.from({ length: 1000 }).map((_, i) => ({
    key: i,
    value: `Item ${i}`,
    avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${i}`
  }))
)
</script>

<style scoped>
.item {
  display: flex;
  align-items: center;
}
.avatar {
  width: 28px;
  border-radius: 50%;
  margin-right: 10px;
}
</style>

Dynamic Size Items

<template>
  <n-virtual-list
    style="max-height: 240px"
    :item-size="42"
    :items="items"
    item-resizable
  >
    <template #default="{ item, index }">
      <div :key="item.key" class="item">
        <img class="avatar" :src="item.avatar" alt="">
        <span>{{ index }} - {{ item.message }}</span>
      </div>
    </template>
  </n-virtual-list>
</template>

Common Patterns

Scroll to Position

<template>
  <n-space vertical>
    <n-space>
      <n-button @click="handleScrollToKey">
        Scroll to Key
      </n-button>
      <n-button @click="handleScrollToPosition">
        Scroll to Position
      </n-button>
      <n-button @click="handleScrollToIndex">
        Scroll to Index
      </n-button>
      <n-button @click="handleScrollToDistance">
        Scroll to Distance
      </n-button>
    </n-space>
    <n-virtual-list
      ref="virtualListInst"
      style="max-height: 240px"
      :item-size="42"
      :items="items"
      item-resizable
    >
      <template #default="{ item, index }">
        <div :key="item.key" class="item" style="height: 42px">
          <img class="avatar" :src="item.avatar" alt="">
          <span>{{ index }}</span>
        </div>
      </template>
    </n-virtual-list>
  </n-space>
</template>

<script setup>
import { ref } from 'vue'

const virtualListInst = ref(null)
const items = ref([])

const handleScrollToKey = () => {
  virtualListInst.value?.scrollTo({ key: 500, behavior: 'smooth' })
}

const handleScrollToPosition = () => {
  virtualListInst.value?.scrollTo({ top: 1000, behavior: 'smooth' })
}

const handleScrollToIndex = () => {
  virtualListInst.value?.scrollTo({ index: 200, behavior: 'smooth' })
}

const handleScrollToDistance = () => {
  virtualListInst.value?.scrollTo({ position: 'bottom', behavior: 'smooth' })
}
</script>

Keep State with Keep-alive

<template>
  <n-space vertical>
    <n-button @click="showVirtualList = !showVirtualList">
      Toggle visibility
    </n-button>
    <keep-alive>
      <n-virtual-list
        v-if="showVirtualList"
        style="max-height: 240px"
        :item-size="42"
        :items="items"
        item-resizable
      >
        <template #default="{ item, index }">
          <div :key="item.key" class="item" style="height: 42px">
            <img class="avatar" :src="item.avatar" alt="">
            <span>{{ index }}</span>
          </div>
        </template>
      </n-virtual-list>
    </keep-alive>
  </n-space>
</template>

<script setup>
import { ref } from 'vue'

const showVirtualList = ref(true)
const items = ref([])
</script>

With Custom Scrollbar

<template>
  <n-virtual-list
    style="max-height: 240px"
    :item-size="42"
    :items="items"
    :scrollbar-props="{ trigger: 'none', size: 10 }"
  >
    <template #default="{ item, index }">
      <div :key="item.key" style="height: 42px; padding: 8px">
        {{ index }}: {{ item.value }}
      </div>
    </template>
  </n-virtual-list>
</template>

Chat Message List

<template>
  <n-virtual-list
    ref="chatListRef"
    style="height: 400px"
    :item-size="60"
    :items="messages"
    item-resizable
    :default-scroll-index="messages.length - 1"
  >
    <template #default="{ item }">
      <div :key="item.id" class="message">
        <div class="sender">{{ item.sender }}</div>
        <div class="content">{{ item.content }}</div>
      </div>
    </template>
  </n-virtual-list>
</template>

<script setup>
import { ref } from 'vue'

const messages = ref([
  { id: 1, sender: 'Alice', content: 'Hello!' },
  { id: 2, sender: 'Bob', content: 'Hi there!' }
])
</script>

Best Practices

  1. Set item-size: Always provide a reasonable item-size estimate for better scroll calculation

  2. Use item-resizable: Enable item-resizable when items have variable heights

  3. Unique keys: Ensure each item has a unique key field

  4. Performance: Use ignore-item-resize if items don't resize for slightly better performance

  5. Keep-alive: Use keep-alive to preserve scroll state when toggling visibility

  6. Debounce scroll: Use debounce: true in scrollTo for smoother scrolling

  7. Scrollbar props: Customize scrollbar via scrollbar-props