Vue 虚拟列表,纵享丝滑【实践篇】
序言
现如今,我们总是在无止境的刷。刷微博、刷抖音、刷沸点......一次次丝滑下拉体验的背后却是前端攻城狮的用心。
本篇讨论基于 Vue.js 的列表无限下拉实践。
我们的目标就是:让列表下拉纵享丝滑,而不是像以往的下拉就 loading 等待的体验。
译自 Better Programming
在线 Demo
设计
咱还是用 Vue CLI 来快速构建项目。
这是主页面:
// 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>
其中核心当然是virtual-list
组件啦~
这里的虚拟列表,我们用到一个三方库 Vue Virtual Scroll List,它在 Github 上有 2.5k+ 的 stars。类比于 react 的 react-virtualized 库。
大量的 DOM 元素会使得我们的网页非常“重”。当 DOM 元素超过 1500 至 2000 个的时候,页面就开始有延迟,尤其是在小型的、性能差的设备上尤为明显。
想象一下,有一个无线滚动的页面,你不断的下拉,它实际上可能形成了上万个 DOM 元素,每个元素还包含子节点,这样将消耗巨大的性能。
Virtual scrollers 正是来解决这个问题的。
如上图,已经表示的很清楚了。列表分为可见区域和缓冲区域,超出这个范围的列表 DOM 都将被删除。
好啦,准备工作已就绪,Let`s get it!
实现
// 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,以便后续使用。
其中,itemComponent
是 virtual-list 的属性,为此我们需要新建一个 SearchResult
子组件,作为搜索结果单元。
代码如下:
// 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>
我们可以通过搜索标题或描述来得到结果,请求数据来源于维基百科。
// search.js
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;
});
}
搜索的方法已经写好,接着就是调用。
当用户键入内容的搜索时候会调用。
当下拉的时候会调用。
// 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>
显而易见,当 searchQuery
变化的时候,我们会得到新的搜索结果。当然,这里的输入框也用到了防抖函数。
另一个需要注意的是,我们第一次搜索加载了两页的结果,用户就会有一定的滚动空间,这样就可以保持顺畅的感觉。
我们在滚动的事件中也加了防抖函数。这里设一个疑问:为什么要在 window.onscroll
事件下设置 leading
为 true
?
然后我们运行程序看效果:
npm run dev
如何?只要你不是疯狂下拉,基本上感受不到 loading 的过程~
小结
用户不会希望每下拉十条结果就要等待新的十条结果加载出来!所以我们需要有缓冲区,还未下拉到底的时候就预判它到底然后提前加载。这便是丝滑体验的内核。
当然不在视图区和缓冲区的 DOM 都将被删除,这也是页面不形成大量 DOM 元素的精髓。
这样动态的处理列表的确是编程人员的一种智慧和用心。
你可以把 项目 克隆到本地再体会一下。以上便是本次分享~