PXD

Virtual List

Display unlimited data lists and maintain page performance

Fixed height items

Item 0
Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
Item 10
Item 11
Item 12
Item 13
Item 14
Item 15
Item 16
Item 17
Item 18
Item 19
Item 20
Item 21
Item 22
Item 23
Item 24
Item 25
Item 26
Item 27
Item 28
Item 29
Item 30
Item 31
Item 32
Item 33
Item 34
Item 35
Item 36
Item 37
Item 38
Item 39
Item 40
Item 41
Item 42
Item 43
Item 44
Item 45
Item 46
Item 47
Item 48
Item 49
Item 50
Item 51
Item 52
Item 53
Item 54
Item 55
Item 56
Item 57
Item 58
Item 59
Item 60
Item 61
Item 62
Item 63
Item 64
Item 65
Item 66
Item 67
Item 68
Item 69
Item 70
Item 71
Item 72
Item 73
Item 74
Item 75
Item 76
Item 77
Item 78
Item 79
Item 80
Item 81
Item 82
Item 83
Item 84
Item 85
Item 86
Item 87
Item 88
Item 89
Item 90
Item 91
Item 92
Item 93
Item 94
Item 95
Item 96
Item 97
Item 98
Item 99
<script setup>
const items = Array.from({ length: 100 }, (_, i) => ({
  id: i,
  name: `Item ${i}`,
}))
</script>

<template>
  <PVirtualList
    class="!h-32 w-100 max-w-full border border-dashed rounded-lg"
    :items="items"
    data-key="id"
    :item-size="40"
  >
    <template #item="{ item }">
      <div class="px-4 h-10 py-2">
        {{ item.name }}
      </div>
    </template>
  </PVirtualList>
</template>

Dynamic height items

Set an approximate height for each item.

Item 0
Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
Item 10
Item 11
Item 12
Item 13
Item 14
Item 15
Item 16
Item 17
Item 18
Item 19
Item 20
Item 21
Item 22
Item 23
Item 24
Item 25
Item 26
Item 27
Item 28
Item 29
Item 30
Item 31
Item 32
Item 33
Item 34
Item 35
Item 36
Item 37
Item 38
Item 39
Item 40
Item 41
Item 42
Item 43
Item 44
Item 45
Item 46
Item 47
Item 48
Item 49
Item 50
Item 51
Item 52
Item 53
Item 54
Item 55
Item 56
Item 57
Item 58
Item 59
Item 60
Item 61
Item 62
Item 63
Item 64
Item 65
Item 66
Item 67
Item 68
Item 69
Item 70
Item 71
Item 72
Item 73
Item 74
Item 75
Item 76
Item 77
Item 78
Item 79
Item 80
Item 81
Item 82
Item 83
Item 84
Item 85
Item 86
Item 87
Item 88
Item 89
Item 90
Item 91
Item 92
Item 93
Item 94
Item 95
Item 96
Item 97
Item 98
Item 99
<script setup>
const items = Array.from({ length: 100 }, (_, i) => ({
  key: i,
  name: `Item ${i}`,
  height: Math.random() * 100 + 40,
}))
</script>

<template>
  <PVirtualList
    class="!h-32 w-100 max-w-full border border-dashed rounded-lg"
    :items="items"
    data-key="key"
    :item-size="40"
  >
    <template #item="{ item }">
      <div class="px-4 py-2" :style="{ height: `${item.height}px` }">
        {{ item.name }}
      </div>
    </template>
  </PVirtualList>
</template>

Masonry layout

Set column-count for column count and column-gap for spacing.

Card 0
Card 1
Card 2
Card 3
Card 4
Card 5
Card 6
Card 7
Card 8
Card 9
Card 10
Card 11
Card 12
Card 13
Card 14
Card 15
Card 16
Card 17
Card 18
Card 19
Card 20
Card 21
Card 22
Card 23
Card 24
Card 25
Card 26
Card 27
Card 28
Card 29
Card 30
Card 31
Card 32
Card 33
Card 34
Card 35
Card 36
Card 37
Card 38
Card 39
Card 40
Card 41
Card 42
Card 43
Card 44
Card 45
Card 46
Card 47
Card 48
Card 49
<script setup>
const items = Array.from({ length: 50 }, (_, i) => ({
  id: i,
  title: `Card ${i}`,
  height: Math.floor(Math.random() * 120) + 72,
}))
</script>

<template>
  <PVirtualList
    class="!h-64 w-100 max-w-full rounded-lg"
    :items="items"
    :column-count="3"
    :column-gap="12"
    :item-size="80"
  >
    <template #item="{ item }">
      <div
        class="rounded-lg border bg-gray-100 px-3 py-2 text-sm"
        :style="{ height: `${item.height}px` }"
      >
        {{ item.title }}
      </div>
    </template>
  </PVirtualList>
</template>

Infinite scroll

Use the status parameter together with the on-bottom callback and the retry event to achieve infinite scrolling of data loading

Loading...
<script setup>
import { ref, onMounted } from 'vue'

const items = ref([])
const listStatus = ref() // 'loading' | 'finished' | 'error'

async function onBottom() {
  const startIdx = items.value.length

  if (startIdx >= 100) {
    listStatus.value = 'finished'
    return
  }

  listStatus.value = 'loading'

  // mock async request
  await new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() < 0.25) {
        listStatus.value = 'error'
      }

      resolve()
    }, 500)
  })

  if (listStatus.value === 'error') {
    return
  }

  items.value.push(
    ...Array.from({ length: 10 }, (_, i) => ({
      id: i,
      name: `Item ${startIdx + i}`,
    }))
  )

  listStatus.value = ''
}

function onRetry() {
  listStatus.value = ''
  onBottom()
}

onMounted(() => {
  onBottom()
})
</script>

<template>
  <PVirtualList
    class="!h-32 w-100 max-w-full border border-dashed rounded-lg"
    data-key="id"
    :item-size="40"
    :status="listStatus"
    :items="items"
    @retry="onRetry"
    @bottom="onBottom"
  >
    <template #item="{ item }">
      <div class="px-4 h-10 py-2">
        {{ item.name }}
      </div>
    </template>
  </PVirtualList>
</template>

Events

NameTypeDescription
retry() => voidEmitted when the message row is clicked while status is error.

Reaching the bottom is reported through the on-bottom prop, not an event.

Slots

NameDescription
itemitem slot
messagemessage slot

Methods

NameTypeDescription
totalSizenumberTotal scrollable size of the list in pixels, 0 while the list is disabled.
virtualItemsVirtualListItem[]Items the virtualizer currently renders, empty while the list is disabled.
getVirtualizer() => Virtualizer<HTMLElement, HTMLElement>Get the underlying TanStack virtualizer instance.
scrollToIndex(index: number, options?: ScrollToIndexOptions) => voidScroll to the item at the given index.
scrollToOffset(toOffset: number, options?: ScrollToOffsetOptions) => voidScroll to an absolute pixel offset.
scrollBy(delta: number, options?: ScrollToOffsetOptions) => voidScroll by a pixel delta.

Source

Source