SoFunction
Updated on 2025-04-11

Vue3 example code to implement virtual list

Preface

The virtual list of this article is based on the previous articleAnalysis of the principle of dynamic high virtual listThe core code and Vue3 implementation are understood aboveThinking & DesignPrerequisites for some content.

use

Install

npm install @/virtual-scroll-vue

Import explicitly in or on demand in components

// 
import VirtualScroll from '@/virtual-scroll-vue'
(VirtualScroll)

// or// 
import { VirtualScroll } from '@/virtual-scroll-vue'

Import styles

// 
import '@/virtual-scroll-vue/dist/'

Github repository:virtual-scroll-vue

Props parameters

parameter type default value Is it necessary describe
items VirtualScrollItem[] - ✔️ List data
placeholder VirtualScrollItem - Simulation data for the smallest child item
startPosition [number, number] [0, 0] List initial position
preserved number - Minimum height of child item
padding number 100 Pre-rendered area height
type VirtualScrollItem = {
  key?: any
  height?: number // Can specify element height and have the highest priority  [k: string | symbol]: any
}

NoticepreservedThe priority is higher thanplaceholder

expose method

method parameter Return value type describe
scroll delta: number, duration?: number - Scroll the specified distance
transport newStartPosition: [number, number] - Transfer to the specified location
getPosition - [number, number] Get the current location of the list

Things to note

Let the child have a unique key

Since the list is based on v-for rendering children, having a unique key for the children can greatly improve performance:

const items = [
  {
    key: 'ABC',
  },
  {
    key: 'BCD',
  },
]

Do not make the minimum height 0 at any time

Since elements cannot confirm their height before being rendered, the list depends on the minimum height of the subproject to determine the render index range. Although it can passplaceholderSet the minimum height to 0, but this causes the list to render all subsequent children:

<!-- preservedDefault has a minimum value5px,Set as0There will be no effect -->
<VirtualScroll :items="data" :preserved="0">
  <template #default="{ item }">
    <!-- Subproject structure -->
  </template>
</VirtualScroll>

<!-- passplaceholder将最小高度Set as0,This will cause the list to render all elements at once -->
<VirtualScroll :items="data" :placeholder="{}">
  <template #default="{ item }">
    <!-- Height is0Subprojects -->
    <div></div>
  </template>
</VirtualScroll>

Do not add new data to the starting element

Since the render index range of a list is determined by the starting element index, the starting element offset, and the minimum project height, adding a new element to the position before the starting element results in unexpected results:

// Assume that startIndex is 1// Adding new data to the items header will cause the starting element of the list rendering to an item with index of 0 in the old array({
  key: 'CDE',
})

Do not modify the element height in the pre-rendered area

Specifically, do not modify the height of the element near the starting direction of the list arrangement (for example, if the list is arranged from top to bottom, do not modify the height of the element in the pre-rendered area above)

thisnoMandatory, on the contrary, the list still works normally, but for the high changes with transitional effects, it is subject toResizeObserverThe lag of the list may cause slight jitter to cause the user experience to become worse

Used on touch screen devices only

Although the list supports scrolling,Not supported yetScroll bars, paging should be considered on non-touch screen devices such as PCs

Example

The list will automatically get the visible area size, and the width and height are100%, recommended to pass.virtual-scroll_containerDo overwrite, or wrap a container outside the component

The list will pass the data to be rendered through the default scope slot.Natural support for dynamic height

<script setup lang="ts">
import { ref } from 'vue'
import DynamicItem from '@/components/DynamicItem/'
import { VirtualScroll, type VirtualScrollInstance } from '@/virtual-scroll-vue'
import {
  generateRandomFirstWord,
  generateRandomWord,
  lorem,
} from '@/utils/helper'
const defaultItem = { name: 'ab', comment: 'abc', index: -1 }
const items = ref(
  new Array(10000).fill(0).map((_, i) => ({
    key: (),
    name:
      generateRandomFirstWord() +
      (() > 0.5
        ? ' ' + generateRandomWord((() * 8) + 2)
        : ''),
    comment: lorem((() * 5) + 1),
    index: i,
  })),
)

const vlist = ref<VirtualScrollInstance>()
function lighteningScroll(delta: number) {
  !.scroll(delta)
}
</script>

<template>
  <div class="page">
    <div class="scroll_container">
      <VirtualScroll
        ref="vlist"
        :items="items"
        :placeholder="defaultItem"
        :start-position="[1000, 0]"
        :padding="0"
      >
        <template #default="{ item }">
          <DynamicItem
            :index=""
            :name=""
            :comment=""
          ></DynamicItem>
        </template>
      </VirtualScroll>
    </div>
    <button @click="lighteningScroll(-100000)">Upward speed roll test</button>
    <button @click="lighteningScroll(100000)">Downward speed roll test</button>
  </div>
</template>

Thinking & Design

The key to virtual listings isHow to get the list item height, determine the height of each item, and you can determine how many elements are rendered. That is, how to obtain the height of the list item determines the actual performance of the virtual list. Here are three ideas:

1. Fixed step length

In the browserMake a judgment before rendering each frame, if the elements in the virtual list are not enough to fill the entire visual area and there are still subsequent elements that are not rendered, the index that ends rendering is moved backnBit.

  • Advantages: Achieve simple and intuitive
  • Disadvantages: Hyperparameters are introducedn, a more reasonable value needs to be determined according to the actual situation. Larger results in more performance waste, and smaller results in a blank page when the user scrolls quickly.

2. Pre-rendering+ Fixed step length

The principle is no different from the above idea, and its advantages and disadvantages are consistent with the above. It can be considered that when calculating whether the element is sufficient to occupy the visual area, the visual area participating in the calculation is expanded, so that the list can render the elements in advance. Can be inTo some extentBlank problems, but not cure the symptoms and not the root cause. Blank pages will still appear when scrolling at a faster speed (such as triggered by code).

3. Pre-render +Highly predicted

Observation found that the root cause of blank pages isIt is not possible to determine how many elements are needed to fill the visible area, For this purpose, it is possible to predict at most how many subitems need to be rendered backwards through the minimum height of each item, so as to ensure that there are always enough elements to fill the visual area.

  • Advantages: Completely resolved the gap through high prediction
  • Disadvantages: When scrolling quickly, the landing position may not match the expected ones because the actual height may be different from the predicted height.

Predictive implementation

There are two main ways to implement high prediction:

  • When designing, determine the minimum height, the unit is CSS pixels. The simplest implementation and the highest performance, but not friendly to some project structures using relative units
  • Automatically obtain the minimum height according to the project structure. By passing in simulation data of an element with the minimum height, the list dynamically obtains the minimum height, with the best universality

Misplacement processing

Maintain the previous agreement:

Starting locationstartPositionDepend on[startIndex, offset]Dual composition

Rendering informationrenderInfoDepend on[viewHeight, paddingHeight, listHeight]Triple composition

functionmove(startPosition, delta, renderInfo, getHeight) => voidCalculate the new starting position after this movement through the starting position, moving distance, rendering information and height acquisition functions

Altitude prediction can lead to misalignment problems that occur when quickly scrolling, where the location is reached is different from what is expected. Roughly speaking, when the list within a frame scrolls to the unreleased area, it will switch to using the predicted height to continue calculating the starting position of the next frame. When the predicted height is inconsistent with the actual height, the list will "overshift". For example, suppose that an element that is not rendered is actually of110px, but consider it as100px, then the remaining rolling distance will be more10px, this is the root cause of the misalignment.

Since you know the problem, then study itConditions of occurrenceBecome very important:

According to the move functionmoveThe calculation method is known:

existSingle moveMedium, if:

  • upMoving distance is greater thanmax(offset - paddingHeight, 0)+ Predicted height
  • downMoving distance is greater thanlistHeight+ Predicted height

It may lead to misalignment

Since the virtual list is determined by the starting position, the misalignment when scrolling up will befatalof. The reason is to calculate the new oneoffsetThe predicted height is used, but the actual height is greater than the predicted height, causing all subsequent elements to move down. Used hereCustom directive + ResizeObserverThe solution is divided into 3 steps:

v-auto-recordWhen the element is mounted, the height used in this calculation is cached

v-watch-sizeCalled after element height is cachedelementResizePerform processing

elementResizeUpdate the height cache according to the situation and select modifyoffsetOr re-render

// 
export default <Directive>{
  mounted(el, binding) {
    if ( &&  === 'mounted') ?.(el)
  },
  unmounted(el, binding) {
    if ( &&  === 'unmounted') ?.(el)
  },
}

// 
export default <Directive>{
  mounted(el, binding) {
    // ! nextTick ensures that vWatchSize is executed after vAutoRecord    nextTick(() => {
      if ( instanceof Function) (el)
       = new ResizeObserver(() => {
        if ( instanceof Function) (el)
      })
      (el)
    })
  },
  beforeUnmount(el) {
    if () {
      ()
      delete 
    }
  },
}
<li
  v-for="(i, index) in renderRange"
  :key="[i].key || index"
  class="virtual-scroll_item"
  v-watch-size="el => elementResize(i, el)"
  v-auto-record:mounted="el => (i, el)"
  v-auto-record:unmounted="() => (i)"
>
  <slot :item="[i]"></slot>
</li>
const elementResize = (index: number, element: HTMLElement) =&gt; {
  const cur = ().height
  const pre = getHeight(index) // Get out the high cache  let isInPaddingRange = false
  if (cur === pre) return

  // Determine whether the element with a height change is in the preloading range  let offset = [1]
  let itemIndex = [0]
  let height = getHeight(itemIndex)
  while (height &gt;= 0 &amp;&amp; offset &gt; 0) {
    if (itemIndex === index) {
      isInPaddingRange = true
      break
    }
    offset -= height
    height = getHeight(++itemIndex)
  }

  // Update high cache  updateHeight(index)

  if (isInPaddingRange) {
    // If the height-changing element is in the preload interval, add offset to the height-changing amount    [1] += cur - pre
  } else if (cur &lt; pre) {
    // If the height-changing element is not in the preload interval, re-render     = !
  }
}

As for the misalignment problem when scrolling down, this is an inherent limitation of high prediction, so there is no good solution. One possible solution to miss the level is: when scrolling quickly, the user cannot tell what is exactly presented on the page, and can immediately modify the starting position to the target position in the next frame at the end of the scrolling to achieve the illusion of quickly scrolling down to the specified position. However, if there is a number in the list item that can easily cause tricks to be misleading, you may need to consider usingtransportCustom scrolling effect.

The above is the detailed content of the sample code for Vue3 to implement virtual lists. For more information about Vue3 virtual lists, please pay attention to my other related articles!