|
<template> |
|
<div class="container" ref="containerRef" @scroll="containerScroll"> |
|
<div |
|
class="content-virtual-scroll" |
|
:style="{ height: `${virtualHeight}px` }" |
|
> |
|
<div |
|
class="content-item" |
|
:style="{ ...offsetTop(index) }" |
|
v-for="(item, index) in realList" |
|
@click="$log(item)" |
|
> |
|
{{ item }} |
|
</div> |
|
<div class="tips" :style="{...offsetTop(realList.length)}">{{ isLoading ? '正在加载..' : '上拉加载更多' }}</div> |
|
</div> |
|
</div> |
|
</template> |
|
|
|
<script lang="ts"> |
|
import { useRequest } from '@/utils/use-request' |
|
import { computed, defineComponent, onMounted, ref, watch } from 'vue' |
|
|
|
async function request(page = 0) { |
|
return new Promise((resolve, reject) => { |
|
setTimeout(() => { |
|
const list: number[] = new Array(20).fill(1).map((_, i) => page * 20 + i) |
|
resolve(list) |
|
}, 2000) |
|
}) |
|
} |
|
|
|
export default defineComponent({ |
|
setup() { |
|
const itemHeight = 100 |
|
const containerRef = ref() |
|
const startIndex = ref(0) |
|
const scroll = ref(0) |
|
const list = ref<number[]>([]) |
|
const { request: listRequst, isLoading } = useRequest(request) |
|
const virtualHeight = computed(() => list.value.length * itemHeight + 100) |
|
const realList = computed(() => |
|
list.value.slice(startIndex.value, endIndex.value), |
|
) |
|
const endIndex = computed(() => { |
|
// 8 为每次渲染的最大数量 |
|
return startIndex.value + 8 |
|
}) |
|
|
|
watch(endIndex, (val, preVal) => { |
|
if (val >= list.value.length) { |
|
console.log('to insert') |
|
insertList() |
|
} |
|
}) |
|
|
|
watch(scroll, (val) => { |
|
startIndex.value = Math.floor(val / 100) |
|
}) |
|
|
|
async function insertList() { |
|
try { |
|
const newList = await listRequst(Math.floor(list.value.length / 20)) |
|
list.value.push(...newList) |
|
} catch (error) { |
|
|
|
} |
|
} |
|
function containerScroll(e: any) { |
|
const { scrollTop } = e?.target |
|
scroll.value = scrollTop |
|
} |
|
function offsetTop(index: number) { |
|
const y = `${index * 100 + scroll.value - (scroll.value % 100)}px` |
|
return { transform: `translateY(${y})` } |
|
} |
|
onMounted(() => { |
|
insertList() |
|
}) |
|
|
|
return { |
|
realList, |
|
virtualHeight, |
|
containerRef, |
|
isLoading, |
|
offsetTop, |
|
containerScroll, |
|
} |
|
}, |
|
}) |
|
</script> |
|
|
|
<style> |
|
.container { |
|
position: relative; |
|
height: 620px; |
|
overflow-y: auto; |
|
} |
|
.content-virtual-scroll { |
|
position: absolute; |
|
top: 0; |
|
right: 0; |
|
height: 100%; |
|
width: 100%; |
|
} |
|
.content-item { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
height: 100px; |
|
width: 100%; |
|
text-align: center; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
box-sizing: border-box; |
|
background: linear-gradient(to bottom, #fff, teal); |
|
} |
|
.tips { |
|
padding: 30px 0; |
|
text-align: center; |
|
color: #ddd; |
|
font-size: 24px; |
|
} |
|
</style> |