Vue 虛擬列表的實(shí)戰(zhàn)示例
序言
現(xiàn)如今,我們總是在無止境的刷。刷微博、刷抖音、刷沸點(diǎn)......一次次絲滑下拉體驗(yàn)的背后卻是前端攻城獅的用心。
本篇討論基于 Vue.js 的列表無限下拉實(shí)踐。
我們的目標(biāo)就是:讓列表下拉縱享絲滑,而不是像以往的下拉就 loading 等待的體驗(yàn)。
設(shè)計(jì)
咱還是用 Vue CLI 來快速構(gòu)建項(xiàng)目。
這是主頁面:
// EndlessList.vue
<template> <div class="endless-scrolling-list"> <!-- 搜索框 --> <div class="search-box"> <input type="text" v-model="searchQuery"/> </div> <p class="center" v-if="results.length == 0 && !loading"> Start typing to search something. </p> <!-- 虛擬列表 --> <virtual-list :data-key="'pageid'" :data-sources="results" :data-component="itemComponent" :page-mode="true" /> <!-- loading --> <loader v-if="loading" /> </div> </template>
其中核心當(dāng)然是virtual-list組件啦~
這里的虛擬列表,我們用到一個(gè)三方庫 Vue Virtual Scroll List,它在 Github 上又 2.5k+ 的 stars。類比于 react 的 react-virtualized 庫。

大量的 DOM 元素會(huì)使得我們的網(wǎng)頁非?!爸亍?。當(dāng) DOM 元素超過 1500 至 2000 個(gè)的時(shí)候,頁面就開始又延遲,尤其是在小型的、性能差的設(shè)備上尤為明顯。
想象一下,有一個(gè)無線滾動(dòng)的頁面,你不斷的下拉,它實(shí)際上可能形成了上萬個(gè) DOM 元素,每個(gè)元素還包含子節(jié)點(diǎn),這樣將消耗巨大的性能。
Virtual scrollers 正是來解決這個(gè)問題的。
如上圖,已經(jīng)表示的很清楚了。列表分為可見區(qū)域和緩沖區(qū)域,超出這個(gè)范圍的列表 DOM 都將被刪除。
好啦,準(zhǔn)備工作已就緒,Let`s get it!
實(shí)現(xiàn)
// imports.js(EndlessList.vue)
import axios from 'axios';
import lodash from 'lodash';
import VirtualList from 'vue-virtual-scroll-list';
import SearchResult from './SearchResult';
import Loader from './Loader';
export default {
name: 'EndlessList',
components: {
VirtualList,
Loader
},
data() {
return {
searchQuery: '',
currentPage: 0,
results: [],
itemComponent: SearchResult,
loading: false
}
},
};
我們引入第三方庫 axios 和 loadsh,以便后續(xù)使用。
其中,itemComponent 是 virtual-list 的屬性,為此我們需要新建一個(gè) SearchResult 子組件,作為搜索結(jié)果單元。
代碼如下:
// SearchResult.vue
<template>
<div class="list-item">
<h3>
{{ source.title }}
</h3>
<div v-html="source.snippet"></div>
</div>
</template>
<script>
export default {
props: {
index: {
// index of current item
type: Number,
},
source: {
type: Object,
default() {
return {};
},
},
},
};
</script>
<style scoped>
.list-item {
padding: 0 10px 20px 10px;
}
.list-item h3 {
margin: 0;
padding-bottom: 10px;
}
</style>
我們可以通過搜索標(biāo)題或描述來得到結(jié)果,請求數(shù)據(jù)來源于維基百科。
search(query, page) {
// We prepare the data that the Wikipedia API expects.
const data = {
action: "query",
format: "json",
list: "search",
continue: "-||",
utf8: 1,
srsearch: query,
sroffset: page * 10,
origin: "*",
};
// And then we convert these params TO GET params in the format
// action=query&format=json ...
const params = Object.keys(data)
.map(function(k) {
return data[k] == ""
? ""
: encodeURIComponent(k) + "=" + encodeURIComponent(data[k]);
})
.join("&");
// We prepare the url with the params string
const searchUrl = `https://en.wikipedia.org/w/api.php?${params}`;
// we set loading to true so that we can display the loader
this.loading = true;
// Then we execute the request and concatenate the results
axios.get(searchUrl).then((response) => {
this.results = this.results.concat(response.data.query.search);
// And of course set loading to false to hide the loader.
this.loading = false;
});
}
搜索的方法已經(jīng)寫好,接著就是調(diào)用。
- 當(dāng)用戶鍵入內(nèi)容的搜索時(shí)候會(huì)調(diào)用。
- 當(dāng)下拉的時(shí)候會(huì)調(diào)用。
// EndlessList.vue
<script>
export default {
// data() and methods skipped for brevity
watch: {
searchQuery: {
immediate: true,
handler: lodash.debounce(function (newVal) {
if (newVal == "") {
return;
}
this.results = [];
this.currentPage = 0;
this.search(newVal, this.currentPage);
this.search(newVal, this.currentPage + 1);
this.currentPage = 2;
}, 200),
},
},
mounted() {
const vm = this;
window.onscroll = lodash.debounce(function () {
var distanceFromBottom =
document.body.scrollHeight - window.innerHeight - window.scrollY;
if (distanceFromBottom < 400 && vm.searchQuery !== "") {
vm.search(vm.searchQuery, vm.currentPage);
vm.currentPage++;
}
}, 100, {leading: true});
},
}
</script>
顯而易見,當(dāng) searchQuery 變化的時(shí)候,我們會(huì)得到新的搜索結(jié)果。當(dāng)然,這里的輸入框也用到了防抖函數(shù)。
另一個(gè)需要注意的是,我們第一次搜索加載了兩頁的結(jié)果,用戶就會(huì)有一定的滾動(dòng)空間,這樣就可以保持順暢的感覺。
我們在滾動(dòng)的事件中也加了防抖函數(shù)。這里設(shè)一個(gè)疑問:為什么要在 window.onscroll 事件下設(shè)置 leading 為 true ?
然后我們運(yùn)行程序看效果:

npm run dev

如何?只要你不是瘋狂下拉,基本上感受不到 loading 的過程~
小結(jié)
用戶不會(huì)希望每下拉十條結(jié)果就要等待新的十條結(jié)果加載出來!所以我們需要有緩沖區(qū),還未下拉到底的時(shí)候就預(yù)判它到底然后提前加載。這便是絲滑體驗(yàn)的內(nèi)核。
當(dāng)然不在視圖區(qū)和緩沖區(qū)的 DOM 都將被刪除,這也是頁面不形成大量 DOM 元素的精髓。
這樣動(dòng)態(tài)的處理列表的確是編程人員的一種智慧和用心。
你可以把 項(xiàng)目 克隆到本地再體會(huì)一下。以上便是本次分享~
以上就是Vue 虛擬列表的實(shí)現(xiàn)示例的詳細(xì)內(nèi)容,更多關(guān)于Vue 虛擬列表的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue實(shí)現(xiàn)移動(dòng)端table表格簡單方法
這篇文章主要給大家介紹了關(guān)于vue實(shí)現(xiàn)移動(dòng)端table表格簡單方法的相關(guān)資料,使用Vue.js開發(fā)移動(dòng)應(yīng)用程序時(shí),經(jīng)常需要使用各種UI組件,其中el-table是一個(gè)常用的表格組件,可以方便地展示數(shù)據(jù),需要的朋友可以參考下2023-09-09
在vue中獲取wangeditor的html和text的操作
這篇文章主要介紹了在vue中獲取wangeditor的html和text的操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10
在vue項(xiàng)目中集成graphql(vue-ApolloClient)
這篇文章主要介紹了在vue項(xiàng)目中集成graphql(vue-ApolloClient),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-09-09
uniapp實(shí)現(xiàn)webview頁面關(guān)閉功能的代碼示例
uniapp用web-view打開一個(gè)網(wǎng)頁,網(wǎng)頁中點(diǎn)擊跳轉(zhuǎn)到下一層級的網(wǎng)頁,一層層深入,點(diǎn)擊返回鍵或者頁面上方返回按鈕只能一層層往回退,下面這篇文章主要給大家介紹了關(guān)于uniapp實(shí)現(xiàn)webview頁面關(guān)閉功能的相關(guān)資料,需要的朋友可以參考下2024-03-03
vue實(shí)現(xiàn)在進(jìn)行增刪改操作后刷新頁面
這篇文章主要介紹了vue實(shí)現(xiàn)在進(jìn)行增刪改操作后刷新頁面,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08
vue+echarts定時(shí)重新繪制以達(dá)到刷新的動(dòng)效問題
這篇文章主要介紹了vue+echarts定時(shí)重新繪制以達(dá)到刷新的動(dòng)效問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06
vue滾動(dòng)條滾動(dòng)到頂部或者底部的方法
這篇文章主要給大家介紹了關(guān)于vue滾動(dòng)條滾動(dòng)到頂部或者底部的相關(guān)資料,文中通過代碼示例介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08
vue學(xué)習(xí)筆記之給組件綁定原生事件操作示例
這篇文章主要介紹了vue學(xué)習(xí)筆記之給組件綁定原生事件操作,結(jié)合實(shí)例形式詳細(xì)分析了vue.js組件綁定原生事件相關(guān)原理、實(shí)現(xiàn)方法與操作注意事項(xiàng),需要的朋友可以參考下2020-02-02

