vue簡單實現(xiàn)一個虛擬列表的示例代碼
一、前言
當(dāng)今的時代是大數(shù)據(jù)時代,往往一個列表就有成千上萬條數(shù)據(jù),而我們一一渲染的話,則需要耗費大量時間,導(dǎo)致網(wǎng)頁打開緩慢。懶加載雖然減少了第一次渲染時間,加快了網(wǎng)頁打開速度,但隨著后續(xù)數(shù)據(jù)的不斷載入拼接,列表的渲染時間也會越來越長。虛擬列表則很好的解決了這一問題。
虛擬列表只渲染當(dāng)前可視區(qū)域的列表,并不會將所有的數(shù)據(jù)渲染。下面用Vue簡單實現(xiàn)移動端虛擬列表(并且支持下拉觸底加載效果)
二、代碼實現(xiàn)
準(zhǔn)備下拉數(shù)據(jù):
export default {
data() {
return {
listData: [], // 總數(shù)據(jù)
isLoading: false, // 展示loading
};
},
mounted() {
this.getListData();
},
methods: {
// 獲取數(shù)據(jù)
getListData() {
const count = 20 + this.listData.length;
const start = this.listData.length;
this.isLoading = true;
setTimeout(() => {
for (let i = start; i < count; i++) {
this.listData.push(i);
}
this.isLoading = false;
}, 500);
},
},
};需要準(zhǔn)備內(nèi)外兩個列表容器,外部容器(viewport)固定高度用于生成滾動條,內(nèi)部容器(scrollbar)用于撐開外部容器使得滾動條保持與未使用虛擬列表時一致。
<template>
<div class="viewport" ref="viewport">
<!-- 滾動條 -->
<div class="scrollbar" :style="{ height: listHeight + 'px' }"></div>
<!-- 展示的列表 -->
<div
class="list"
:style="{ transform: `translateY(${transformOffset}px)` }"
>
<div
class="row"
:style="{ height: rowHeight + 'px' }"
v-for="(item, index) in showList"
:key="index"
>
<slot :record="item"></slot>
</div>
</div>
<!-- 加載 -->
<div class="loading_wrap" v-show="loading">
<div class="loading">
<div class="container"></div>
</div>
<div>正在加載中</div>
</div>
</div>
</template>
<style lang="less" scoped>
/*
------最外層容器---------*/
.viewport {
width: 100%;
height: 100%; // 這個的高度讓父組件去決定
background-color: #fff;
position: relative;
overflow-y: auto;
}
/*
------列表展示層容器---------*/
.list {
position: absolute;
top: 0;
left: 0;
right: 0;
}
/*
------每行容器---------*/
.row {
overflow: hidden;
}
</style>計算列表的總高度listHeight(列表的總條數(shù)乘以每一條的高度),可視區(qū)域的高度viewHeight。
計算當(dāng)前可見區(qū)域起始數(shù)據(jù)的startIndex和結(jié)束數(shù)據(jù)的endIndex,監(jiān)聽viewport列表滾動事件,計算currentIndex以及列表的偏移量transformOffset。
監(jiān)聽滾動事件動態(tài)設(shè)置顯示的列表(showList)。
<script lang="ts" setup>
import {
defineProps,
withDefaults,
defineEmits,
ref,
onMounted,
computed,
} from "vue";
interface Props {
list: string[]; // 數(shù)據(jù)源
rowHeight: number; // 每行的高度
viewCount: number; // 顯示數(shù)量
loading: boolean; // 控制loading
}
const props = withDefaults(defineProps<Props>(), {
list: () => [],
rowHeight: 200,
viewCount: 10,
loading: false,
});
const emit = defineEmits<{
(e: "bottomLoad"): void;
}>();
let viewHeight = ref(0); //可視區(qū)域的高度
let startIndex = ref(0); //開始索引
let endIndex = ref(0); //結(jié)束索引
let transformOffset = ref(0); //列表的偏移量
const viewport = ref(null);
onMounted(() => {
initData();
});
let showList = computed(() =>
props.list.slice(startIndex.value, endIndex.value)
); //展示的數(shù)據(jù)
let listHeight = computed(() => props.list.length * props.rowHeight); //列表的總高度
// 初始化一些數(shù)據(jù)
const initData = () => {
endIndex.value = props.viewCount;
viewHeight.value = viewport.value.offsetHeight;
};
// 列表滾動
const onScroll = () => {
const scrollTop = viewport.value.scrollTop; // 獲取試圖往上滾動的高度
const currentIndex = Math.floor(scrollTop / props.rowHeight); // 計算當(dāng)前的索引
// 只在需要變化的時 才重新賦值
if (startIndex.value !== currentIndex) {
startIndex.value = currentIndex;
endIndex.value = startIndex.value + props.viewCount; // 結(jié)束索引
transformOffset.value = scrollTop - (scrollTop % props.rowHeight);
}
// 觸底了
if (Math.round(viewHeight.value + scrollTop) === listHeight.value) {
// 發(fā)送觸底加載事件
emit("bottomLoad");
}
};
</script>三、完整代碼
demo.vue
<template>
<div class="page">
<h3>長列表渲染</h3>
<ListScroll
class="list_scroll"
:list="listData"
:loading="isLoading"
@bottomLoad="onBottomLoad"
>
<template v-slot="{ record }">
<div class="row_content" @click="handleClick(record)">
<div>{{ record }}</div>
<img
class="image"
src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F2076f7ae-d134-4dc4-a865-af1b2029d400%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1680249943&t=7646a71b62c810256a2b414e96106808"
/>
</div>
</template>
</ListScroll>
</div>
</template>
<script>
export default {
data() {
return {
listData: [], // 總數(shù)據(jù)
isLoading: false, // 展示loading
};
},
mounted() {
this.getListData();
},
methods: {
// 獲取數(shù)據(jù)
getListData() {
const count = 20 + this.listData.length;
const start = this.listData.length;
this.isLoading = true;
setTimeout(() => {
for (let i = start; i < count; i++) {
this.listData.push(i);
}
this.isLoading = false;
}, 500);
},
// 監(jiān)聽觸底事件
onBottomLoad() {
console.log("觸底了");
if (this.listData.length >= 100) {
console.log("數(shù)據(jù)加載完了~");
return;
}
// 加載數(shù)據(jù)
this.getListData();
},
// 監(jiān)聽點擊每行
handleClick(record) {
console.log(record, "record");
},
},
};
</script>
<style lang="less" scoped>
.page {
display: flex;
flex-direction: column;
height: 100vh;
.list_scroll {
flex: 1;
}
}
.row_content {
width: 100%;
height: 100%;
.image {
display: block;
width: 100%;
height: 160px;
object-fit: cover;
}
}
</style>ListScroll.vue
<template>
<div class="viewport" ref="viewport" @scroll="onScroll">
<!-- 滾動條 -->
<div class="scrollbar" :style="{ height: listHeight + 'px' }"></div>
<!-- 展示的列表 -->
<div
class="list"
:style="{ transform: `translateY(${transformOffset}px)` }"
>
<div
class="row"
:style="{ height: rowHeight + 'px' }"
v-for="(item, index) in showList"
:key="index"
>
<slot :record="item"></slot>
</div>
</div>
<!-- 加載 -->
<div class="loading_wrap" v-show="loading">
<div class="loading">
<div class="container"></div>
</div>
<div>正在加載中</div>
</div>
</div>
</template>
<script lang="ts" setup>
import {
defineProps,
withDefaults,
defineEmits,
ref,
onMounted,
computed,
} from "vue";
interface Props {
list: string[]; // 數(shù)據(jù)源
rowHeight: number; // 每行的高度
viewCount: number; // 顯示數(shù)量
loading: boolean; // 控制loading
}
const props = withDefaults(defineProps<Props>(), {
list: () => [],
rowHeight: 200,
viewCount: 10,
loading: false,
});
const emit = defineEmits<{
(e: "bottomLoad"): void;
}>();
let viewHeight = ref(0); //可視區(qū)域的高度
let startIndex = ref(0); //開始索引
let endIndex = ref(0); //結(jié)束索引
let transformOffset = ref(0); //列表的偏移量
const viewport = ref(null);
onMounted(() => {
initData();
});
let showList = computed(() =>
props.list.slice(startIndex.value, endIndex.value)
); //展示的數(shù)據(jù)
let listHeight = computed(() => props.list.length * props.rowHeight); //列表的總高度
// 初始化一些數(shù)據(jù)
const initData = () => {
endIndex.value = props.viewCount;
viewHeight.value = viewport.value.offsetHeight;
};
// 列表滾動
const onScroll = () => {
const scrollTop = viewport.value.scrollTop; // 獲取試圖往上滾動的高度
const currentIndex = Math.floor(scrollTop / props.rowHeight); // 計算當(dāng)前的索引
// 只在需要變化的時 才重新賦值
if (startIndex.value !== currentIndex) {
startIndex.value = currentIndex;
endIndex.value = startIndex.value + props.viewCount; // 結(jié)束索引
transformOffset.value = scrollTop - (scrollTop % props.rowHeight);
}
// 觸底了
if (Math.round(viewHeight.value + scrollTop) === listHeight.value) {
// 發(fā)送觸底加載事件
emit("bottomLoad");
}
};
</script>
<style lang="less" scoped>
/*
------最外層容器---------*/
.viewport {
width: 100%;
height: 100%; // 這個的高度讓父組件去決定
background-color: #fff;
position: relative;
overflow-y: auto;
}
/*
------列表展示層容器---------*/
.list {
position: absolute;
top: 0;
left: 0;
right: 0;
}
/*
------每行容器---------*/
.row {
overflow: hidden;
}
/*
------loading樣式---------*/
.loading_wrap {
display: flex;
justify-content: center;
align-items: center;
color: #999;
padding: 20px 0;
.loading {
box-sizing: border-box;
width: 20px;
height: 20px;
border: 2px solid #ddd;
border-radius: 50%;
animation: rotate 1s linear infinite;
margin-right: 10px;
}
.container {
position: relative;
top: 50%;
left: 50%;
width: 10px;
height: 10px;
background-color: #fff;
}
}
/*
------loading動畫---------*/
@keyframes rotate {
from {
transform-origin: center center;
transform: rotate(0deg);
}
to {
transform-origin: center center;
transform: rotate(360deg);
}
}
</style>四、實現(xiàn)效果

五、實現(xiàn)效果
實現(xiàn)虛擬列表就是處理滾動條滾動后的可見區(qū)域的變更,具體實現(xiàn)步驟如下:
- 計算當(dāng)前可見區(qū)域起始數(shù)據(jù)的startIndex
- 計算當(dāng)前可見區(qū)域借宿數(shù)據(jù)的endIndex
- 計算當(dāng)前可見區(qū)域的數(shù)據(jù),并渲染到頁面中
- 計算startIndex對應(yīng)的數(shù)據(jù)在整個列表中的偏移位置transformOffset,并設(shè)置到列表上
到此這篇關(guān)于vue簡單實現(xiàn)一個虛擬列表的示例代碼的文章就介紹到這了,更多相關(guān)vue 虛擬列表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue.js 2.0和Cordova開發(fā)webApp環(huán)境搭建方法
下面小編就為大家分享一篇Vue.js 2.0和Cordova開發(fā)webApp環(huán)境搭建方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-02-02
使用Vue.js和MJML創(chuàng)建響應(yīng)式電子郵件
這篇文章主要介紹了使用Vue.js和MJML創(chuàng)建響應(yīng)式電子郵件,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
Vue中消息橫向滾動時setInterval清不掉的問題及解決方法
最近在做項目時,需要進行兩個組件聯(lián)動,一個輪詢獲取到消息,然后將其傳遞給另外一個組件進行橫向滾動展示,結(jié)果滾動的速度越來越快。接下來通過本文給大家分享Vue中消息橫向滾動時setInterval清不掉的問題及解決方法,感興趣的朋友一起看看吧2019-08-08

