虚拟列表用来解决大数据量数据渲染问题,由于一次性渲染性能低,所以诞生了虚拟列表渲染。该场景下可视区高度都是相对固定的。相对固定是指在一定条件下可以被改变。
虚拟列表的要做的事是确保性能的前提下,利用一定的技术模拟全数据一次性渲染后效果。
主要通过两件事:
1,渲染数据,适时变更渲染数据
2,模拟滚动效果
场景一,可视区高度固定,单条数据高度固定且相同比如element-ui el-select下拉选择框,这是最简单的场景。相关参数固定,也最好实现。
<template>
<div ref="list" class="render-list-container" @scroll="scrollEvent($event)">
<div class="render-list-phantom" :style="{ height: listHeight 'px' }"></div>
<div class="render-list" :style="{ transform: getTransform }">
<template
v-for="item in visibleData"
>
<slot :value="item.value" :height="item.height 'px'" :index="item.id"></slot>
</template>
</div>
</div>
</template>
<script>
export default {
name: 'VirtualList',
props: {
// 所有列表数据
listData: {
type: Array,
default: () => []
},
// 每项高度
itemSize: {
type: Number,
default: 50
}
},
computed: {
// 列表总高度
listHeight () {
return this.listData.length * this.itemSize
},
// 可显示的列表项数
visibleCount () {
return Math.ceil(this.screenHeight / this.itemSize)
},
// 偏移量对应的style
getTransform () {
return `translate3d(0,${this.startOffset}px,0)`
},
// 获取真实显示列表数据
visibleData () {
return this.listData.slice(this.start, Math.min(this.end, this.listData.length))
}
},
mounted () {
this.screenHeight = this.$el.clientHeight
this.start = 0
this.end = this.start this.visibleCount
},
data () {
return {
// 可视区域高度
screenHeight: 0,
// 偏移量
startOffset: 0,
// 起始索引
start: 0,
// 结束索引
end: null
}
},
methods: {
scrollEvent () {
// 当前滚动位置
const scrollTop = this.$refs.list.scrollTop
// 此时的开始索引
this.start = Math.floor(scrollTop / this.itemSize)
// 此时的结束索引
this.end = this.start this.visibleCount
// 此时的偏移量
this.startOffset = scrollTop - (scrollTop % this.itemSize)
}
}
}
</script>
<style scoped>
.render-list-container {
overflow: auto;
position: relative;
-webkit-overflow-scrolling: touch;
height: 200px;
}
.render-list-phantom {
position: absolute;
left: 0;
right: 0;
z-index: -1;
}
.render-list {
text-align: center;
}
</style>
初始化渲染数据
单条数据高度确定,可视区高度确定。可以用二者计算出显示条数。初始索引为0,结束索引为显示条数。之后截取总数据当中对应的数据即可。
// 可显示的列表项数
visibleCount () {
return Math.ceil(this.screenHeight / this.itemSize)
},
...
mounted () {
// 开始索引
this.start = 0
// 结束索引
this.end = this.start this.visibleCount
},
滚动逻辑
可视区高度确定,列表需要模拟出滚动条,这里采用占位div撑开方案。随着滚动条滚动,监听滚动高度计算出开始索引和结束索引,再计算出滚动偏移量,再利用translate3d滚动,translate3d且因为没有重排重绘,所以性能更好。
methods: {
scrollEvent () {
// 当前滚动位置
const scrollTop = this.$refs.list.scrollTop
// 此时的开始索引
this.start = Math.floor(scrollTop / this.itemSize)
// 此时的结束索引
this.end = this.start this.visibleCount
// 此时的偏移量
this.startOffset = scrollTop - (scrollTop % this.itemSize)
}
}
再渲染逻辑
随着滚动条滚动,监听滚动高度计算出开始索引和结束索引,重置开始和结束索引,也就自然引发Vue重新渲染。
场景二,可视区高度固定,单条数据高度确定但不相同如果单条数据不固定,一定是因为有不同的数据展示方式,每种方式可以封装成组件,之后动态展示。这块封装了三个组件,高度分别为20px、30px、50px。
封装组件<template>
<div style="height:20px;border:1px solid #333;">
height:20px;---{{ index }}
</div>
</template>
<script>
export default {
props: ['index']
}
</script>
<template>
<div style="height:30px;border:1px solid #333;">
height: 30px---{{ index }}
</div>
</template>
<script>
export default {
props: ['index']
}
</script>
<template>
<div style="height:50px;border:1px solid #333;">
height: 50px--{{ index }}
</div>
</template>
<script>
export default {
props: ['index']
}
</script>
整体实现
<template>
<div ref="list" class="render-list-container" @scroll="scrollEvent($event)">
<div
class="render-list-phantom"
:style="{ height: listHeight 'px' }"
></div>
<div class="render-list" :style="{ transform: getTransform }">
<template v-for="item in visibleData">
<slot :type="item.type" :index="item.id"></slot>
</template>
</div>
</div>
</template>
<script>
export default {
name: 'VirtualList',
props: {
// 所有列表数据
listData: {
type: Array,
default: () => []
}
},
computed: {
// 列表总高度
listHeight () {
return this.listData.reduce((acc, curVal) => {
return acc curVal.height
}, 0)
},
// 可显示的列表项数
visibleCount () {
let accHeight = 0
let count = 0
for (let i = 0; i < this.listData.length; i ) {
accHeight = this.listData[i].height
if (accHeight >= this.screenHeight) {
count
break
}
count
}
return count
},
// 偏移量对应的style
getTransform () {
return `translate3d(0,${this.startOffset}px,0)`
},
// 获取真实显示列表数据
visibleData () {
return this.listData.slice(
this.start,
Math.min(this.end, this.listData.length)
)
}
},
mounted () {
this.screenHeight = this.$el.clientHeight
this.end = this.start this.visibleCount
},
data () {
return {
// 可视区域高度
screenHeight: 0,
// 偏移量
startOffset: 0,
// 起始索引
start: 0,
// 结束索引
end: null
}
},
methods: {
getStart (scrollTop) {
var height = 0
var start = 0
var i = 0
while (true) {
const currentItem = this.listData[i].height
if (currentItem) {
height = currentItem
if (height >= scrollTop) {
start = i
break
}
} else {
break
}
i
}
return start
},
scrollEvent () {
// 当前滚动位置
const scrollTop = this.$refs.list.scrollTop
// 此时的开始索引
this.start = this.getStart(scrollTop)
// 此时的结束索引
this.end = this.start this.visibleCount
const offsetHeight = scrollTop - (this.visibleData.reduce((acc, curVal) => acc curVal.height, 0) - this.screenHeight)
// 此时的偏移量
this.startOffset = offsetHeight < 0 ? 0 : offsetHeight
}
}
}
</script>
<style scoped>
.render-list-container {
overflow: auto;
position: relative;
-webkit-overflow-scrolling: touch;
height: 200px;
}
.render-list-phantom {
position: absolute;
left: 0;
right: 0;
z-index: -1;
}
.render-list {
text-align: center;
}
</style>
初始化渲染逻辑
通过累加组件高度的方式算出初始化显示条数,初始化开始索引为0,结束索引为显示条数
// 可显示的列表项数
visibleCount () {
let accHeight = 0
let count = 0
for (let i = 0; i < this.listData.length; i ) {
accHeight = this.listData[i].height
if (accHeight >= this.screenHeight) {
count
break
}
count
}
return count
},
滚动逻辑
依然采用占位div撑开方案保证可以滚动。随着滚动条滚动,监听滚动高度,再通过累加方式算出最新的开始索引和结束索引,算出滚动偏移量
methods: {
getStart (scrollTop) {
var height = 0
var i = 0
while (true) {
const currentItem = this.listData[i].height
height = currentItem
if (height >= scrollTop) {
start = i
break
}
i
}
return i
},
scrollEvent () {
// 当前滚动位置
const scrollTop = this.$refs.list.scrollTop
// 此时的开始索引
this.start = this.getStart(scrollTop)
// 此时的结束索引
this.end = this.start this.visibleCount
const offsetHeight = scrollTop - (this.visibleData.reduce((acc, curVal) => acc curVal.height, 0) - this.screenHeight)
// 此时的偏移量
this.startOffset = offsetHeight < 0 ? 0 : offsetHeight
}
}
再渲染逻辑
监听滚动高度计算出开始索引和结束索引,重置开始和结束索引,也就自然引发Vue重新渲染。
测试<template>
<div class="render-show">
<div>
<VirtualList :listData="data">
<template slot-scope="{type, index}">
<component :is="type" :index="index"></component>
</template>
</VirtualList>
</div>
</div>
</template>
<script>
import VirtualList from './parts/VirtualList'
import Height20 from './parts/Height20'
import Height30 from './parts/Height30'
import Height50 from './parts/Height50'
const d = []
for (let i = 0; i < 1000; i ) {
const type = i % 3 === 0 ? i % 2 === 0 ? 'Height30' : 'Height50' : 'Height20'
d.push({ id: i, value: i, type: type, height: type === 'Height30' ? 30 : type === 'Height20' ? 20 : 50 })
}
export default {
name: 'VirtualList-test',
data () {
return {
data: d
}
},
components: {
VirtualList,
Height20,
Height30,
Height50
}
}
</script>
<style>
.render-show {
display: flex;
justify-content: center;
}
.render-show > div{
width:500px;
margin-top:40px;
}
.render-list-item {
color: #555;
box-sizing: border-box;
border-bottom: 1px solid #999;
box-sizing: border-box;
}
</style>
VeryCapture_20230901220330.gif
场景三 以上两种情况追加数据以上两种场景。如何追加数据呢?
scrollEvent () {
// 当前滚动位置
const scrollTop = this.$refs.list.scrollTop
// 此时的开始索引
this.start = this.getStart(scrollTop)
// 此时的结束索引
this.end = this.start this.visibleCount
const offsetHeight = scrollTop - (this.visibleData.reduce((acc, curVal) => acc curVal.height, 0) - this.screenHeight)
// 此时的偏移量
this.startOffset = offsetHeight < 0 ? 0 : offsetHeight
// 追加数据
if (this.end 1 >= this.listData.length) {
this.$emit('appendData', this.listData.length)
}
}
...
appendData (start) {
const d = []
for (let i = start; i < start 10; i ) {
const type = i % 3 === 0 ? i % 2 === 0 ? 'Height30' : 'Height50' : 'Height20'
d.push({ id: i, value: i, type: type, height: type === 'Height30' ? 30 : type === 'Height20' ? 20 : 50 })
}
this.data = [...this.data, ...d]
}
注意事项
以上两个场景中均对偏移量做了处理
this.startOffset = scrollTop - (scrollTop % this.itemSize)
const offsetHeight = scrollTop - (this.visibleData.reduce((acc, curVal) => acc curVal.height, 0) - this.screenHeight)
// 此时的偏移量
this.startOffset = offsetHeight < 0 ? 0 : offsetHeight
真实的滚动就是滚动条滚动了多少,可视区就向上移动多少。但虚拟滚动不是的。当起始索引发生变化时,渲染数据发生变化了,但渲染数据的高度不是连续的,所以需要动态的设置偏移量。当滚动时起始索引不发生变化时,此时可以什么也不做,滚动显示的内容由浏览器控制。
关注公众号:通往自由之路pro,了解更多关于个人进步副业投资的心得,共同进步
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved