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 }
Notice:preserved
The 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 passplaceholder
Set 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 toResizeObserver
The 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_container
Do 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 backn
Bit.
- Advantages: Achieve simple and intuitive
- Disadvantages: Hyperparameters are introduced
n
, 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 locationstartPosition
Depend on[startIndex, offset]
Dual composition
Rendering informationrenderInfo
Depend on[viewHeight, paddingHeight, listHeight]
Triple composition
functionmove(startPosition, delta, renderInfo, getHeight) => void
Calculate 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 functionmove
The calculation method is known:
existSingle moveMedium, if:
-
upMoving distance is greater than
max(offset - paddingHeight, 0)
+ Predicted height -
downMoving distance is greater than
listHeight
+ 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 oneoffset
The 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-record
When the element is mounted, the height used in this calculation is cached
v-watch-size
Called after element height is cachedelementResize
Perform processing
elementResize
Update the height cache according to the situation and select modifyoffset
Or 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) => { 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 >= 0 && offset > 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 < 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 usingtransport
Custom 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!