Skip to content

Instantly share code, notes, and snippets.

@ubbcou
Last active July 29, 2021 06:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ubbcou/5e00eedfe7340e2ec0d701324561d2e1 to your computer and use it in GitHub Desktop.
Save ubbcou/5e00eedfe7340e2ec0d701324561d2e1 to your computer and use it in GitHub Desktop.
虚拟滚动列表的思路与实现

虚拟滚动列表的思路与实现

/**
 * @target 将第三方table组件改造成可无限加载的虚拟列表
 * @var 滚动区域 scroll-area
 * @var 实际显示区域 real-area
 * @var 滚动偏移量 scroll-offset
 * @result real-area 设置 scroll-offset,使它一直保持在可视区
 */
<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>
import { ref } from "vue"
export function useRequest(originRequest: (arg: any) => Promise<any>) {
const isLoading = ref(false)
const isEnded = ref(false)
async function request(data: any) {
if (isLoading.value) {
return Promise.reject('')
}
isLoading.value = true
try {
const response = await originRequest(data)
isLoading.value = false
return response
} catch (error) {
isLoading.value = false
return Promise.reject(error)
}
}
return {
isLoading,
isEnded,
request
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment