一、使用 transform
<template>
<div ref="list" class="virtural-list-container" @scroll="scrollEvent">
<div class="virtural-list-phantom" :style="{ height: listHeight + 'px' }"></div>
<div class="virtural-list" :style="{ transform: getTransform }">
<div
v-for="item in visibleData"
:key="item.id"
class="virtural-list-item"
:style="{ height: itemHeight + 'px', lineHeight: itemHeight + 'px' }"
>
<span>{{ item.value }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'VirturalList1',
data() {
return {
listData: [],
itemHeight: 50,
screenHeight: 0,
curOffset: 0,
startIndex: 0,
}
},
computed: {
visibleData() {
return this.listData.slice(this.startIndex, Math.min(this.endIndex, this.listData.length))
},
listHeight() {
return this.listData.length * this.itemHeight
},
visibleCount() {
return Math.ceil(this.screenHeight / this.itemHeight)
},
getTransform() {
return `translate3d(0, ${this.curOffset}px, 0)`
},
endIndex() {
return this.startIndex + this.visibleCount
},
},
mounted() {
this.init()
},
methods: {
init() {
this.getData()
this.screenHeight = this.$el.clientHeight
this.startIndex = 0
},
async getData() {
const request = () => {
return new Promise((resolve) => {
let data = []
for (let i = 0; i < 10000; i++) {
data.push({
id: i,
value: `列表内容${i}`,
})
}
setTimeout(() => {
resolve({
code: 200,
data,
msg: 'Success',
})
})
})
}
const res = await request()
if (res.code === 200) {
this.listData = res.data
}
},
scrollEvent() {
const scrollTop = this.$refs.list.scrollTop
this.startIndex = Math.floor(scrollTop / this.itemHeight)
this.curOffset = scrollTop - (scrollTop % this.itemHeight)
},
},
}
</script>
<style lang="less" scoped>
.virtural-list-container {
position: relative;
width: 400px;
height: 600px;
overflow: auto;
border: 1px solid #eee;
.virtural-list-phantom {
position: absolute;
left: 0;
right: 0;
top: 0;
}
.virtural-list {
position: absolute;
left: 0;
right: 0;
top: 0;
text-align: center;
&-item {
padding: 10px;
}
.virtural-list-item + .virtural-list-item {
border-top: 1px solid #eee;
}
}
}
</style>
二、使用paddingTop和paddingBottom
<template>
<div ref="list" class="virtural-list-container" @scroll="scrollEvent">
<div class="virtural-list" :style="{ paddingTop: paddingTop + 'px', paddingBottom: paddingBottom + 'px' }">
<div
v-for="item in visibleData"
:key="item.id"
class="virtural-list-item"
:style="{ height: itemHeight + 'px', lineHeight: itemHeight + 'px' }"
>
<span>{{ item.value }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'VirturalList2',
data() {
return {
listData: [],
itemHeight: 50,
screenHeight: 0,
startIndex: 0,
}
},
computed: {
visibleData() {
return this.listData.slice(this.startIndex, Math.min(this.endIndex, this.listData.length))
},
visibleCount() {
return Math.ceil(this.screenHeight / this.itemHeight)
},
paddingTop() {
return this.startIndex * this.itemHeight
},
paddingBottom() {
const bottom = this.listData.length - this.startIndex - this.visibleCount
return (bottom > 0 ? bottom : 0) * this.itemHeight
},
endIndex() {
return this.startIndex + this.visibleCount
},
},
mounted() {
this.init()
},
methods: {
init() {
this.getData()
this.screenHeight = this.$el.clientHeight
this.startIndex = 0
},
async getData() {
const request = () => {
return new Promise((resolve) => {
let data = []
for (let i = 0; i < 10000; i++) {
data.push({
id: i,
value: `列表内容${i}`,
})
}
setTimeout(() => {
resolve({
code: 200,
data,
msg: 'Success',
})
})
})
}
const res = await request()
if (res.code === 200) {
this.listData = res.data
}
},
scrollEvent() {
const scrollTop = this.$refs.list.scrollTop
this.startIndex = Math.floor(scrollTop / this.itemHeight)
},
},
}
</script>
<style lang="less" scoped>
.virtural-list-container {
position: relative;
width: 400px;
height: 600px;
overflow: auto;
border: 1px solid #eee;
.virtural-list {
position: absolute;
left: 0;
right: 0;
top: 0;
text-align: center;
&-item {
padding: 10px;
}
.virtural-list-item + .virtural-list-item {
border-top: 1px solid #eee;
}
}
}
</style>