When you have a large amount of data to display, you can use pagination, but the user doesn’t have a coherent view of the information. At this point, the data can be cached locally and displayed in the form of a virtual list. Here, a simple virtual list is developed using Vue3.
Train of thought
Just to give you an idea, it’s not the only way to do it, it’s not the best way to do it, but the principle is pretty much the same, and it’s relatively easy to understand.
Page structure
<div id="app">
<div class="container">
<div class="empty"></div>
<ul class="list">
<li class="item"></li>
</ul>
</div>
</div>
Copy the code
- Initialize container: Give a containercontainerSet the height, list will be displayed in the container, set overflow scrolling.
- Open container: Set a fixed row height that can be set according to the list
length
Calculate the actual height of the list and set it to adiv
Here is calledempty, so that the container can already start scrolling, but there is no data in the page. - Render list: Calculate how many containers can be displayed based on a fixed line heightitem, the page always shows the fixed number, givecontainerSet up the
display: flex;
And letlist 和 emptySide by side, becauseemptyOnly the height has no width, so it is invisible to the user and only serves to spread outcontainerThe role of.
- Container rolling: When rollingcontainerWhen, according to
scrollTop
Figure out how many there areitemRolled away, set toslice
的start
, recalculate the part that should be cut from the presentation and givelistSet up andscrollTop
The sametranslateY
So that the top of the list is always aligned with the top of the container.
Code implementation
const oriData = Array.from({ length: 1000 }, (v, k) = > k)
const itemHeight = 20
const emptyHeight = itemHeight * oriData.length
const containerHeight = window.innerHeight
const itemCount = Math.ceil(containerHeight / itemHeight)
Copy the code
oriData
Generates an array of length 1000 as fake dataitemHeight
Set the row height to 20, that is, the height occupied by each data itememptyHeight
The actual height that the list should occupycontainerHeight
Height of the containeritemCount
The number of pages displayed at a timeceil
Because let’s say the last line doesn’t show oneitemShow it, too, or there will be an empty space at the bottom of the page
const container = ref(null)
const start = ref(0)
const translateY = ref(0)
const listData = computed(() = > {
return oriData.slice(start.value, start.value + itemCount + 1)})Copy the code
translateY
As the page scrolls,listMove too, or it will disappear from the screenlistData
The data that is always displayed on the page, and the reason for this is that the data in the list changes as you scroll through the screen
onMounted(() = > {
container.value.addEventListener('scroll'.e= > {
const { scrollTop } = e.target
start.value = Math.floor(scrollTop / itemHeight)
translateY.value = scrollTop + 'px'})})Copy the code
When the container rolls, get the scrollTop, assign it to the translateY so that the top of the list is always opposite the top of the container. And figure out how many items we have to scroll by, and the reason we use floor is because if we scroll 39px we should scroll by one item instead of two. The calculation properties above recalculate the array to intercept based on changes in start, and relying on Vue makes this easier.
The complete code
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<title>Document</title>
<style>
* {
margin: 0;
}
.container {
overflow: auto;
display: flex;
}
</style>
</head>
<div id="app">
<div class="container" ref="container" :style="{ height: containerHeight }">
<div class="empty" :style="{ height: emptyHeight }"></div>
<ul class="list" :style="{ transform: `translateY(${translateY})` }">
<li v-for="item in listData" :key="item" class="item" :style="{ height: itemHeight }">{{ item }}</li>
</ul>
</div>
</div>
<body>
<script src="https://unpkg.com/vue@next"></script>
<script>
const { ref, onMounted, computed } = Vue
const App = {
setup() {
const oriData = Array.from({ length: 1000 }, (v, k) = > k)
const itemHeight = 20
const emptyHeight = itemHeight * oriData.length
const containerHeight = window.innerHeight
const itemCount = Math.ceil(containerHeight / itemHeight)
const container = ref(null)
const start = ref(0)
const translateY = ref(0)
const listData = computed(() = > {
return oriData.slice(start.value, start.value + itemCount + 1)
})
onMounted(() = > {
container.value.addEventListener('scroll'.e= > {
const { scrollTop } = e.target
start.value = Math.floor(scrollTop / itemHeight)
translateY.value = scrollTop + 'px'})})return {
listData,
container,
translateY,
containerHeight: containerHeight + 'px'.itemHeight: itemHeight + 'px'.emptyHeight: emptyHeight + 'px'
}
}
}
Vue.createApp(App).mount('#app')
</script>
</body>
</html>
Copy the code
Write in the back
In fact, according to the rendering rules of the virtual DOM, this example performs better without adding a key to the item than with the key