本文章翻译自:https://vueschool.io/articles/vuejs-tutorials/build-an-infinite-scroll-component-using-intersection-observer-api/
开发过程中,经常会遇到需要处理大量数据的情况,比如列表、历史记录等,通常选择无限加载和分页导航。
传统后端渲染,一般会选择分页导航,它可以轻松跳转,甚至一次跳转几个页面,现在SPA盛行,无限滚动加载是更好的方案,可以给用户更好的体验,尤其是在移动端。
在Awesome Vue中,有如下无限滚动组件
- - An infinite scroll plugin for Vue.js 1.0 & Vue.js 2.0.
- - Infinite scroll component for Vue.js 2.
- - An infinite scroll directive for vue.js.
- - An infinite content loop component for Vue.js 2.
- - An infinite content loop component for Vue.js 2, including functionalities such as 'pull-to-refresh', 'infinite-loading', 'snaping-scroll'.
- - An infinite list mixin can recycle dom for Vue.js 2
- - ∞ Infinite slide bar component.
- - A vue2 component based on Iscroll, supports big data list with high performance scroll, infinite load and pull refresh.
Intersection Observer API的出现,让开发无限滚动组件变得更加简单方便。
Intersection Observer API
Intersection Observer API提供了一个可订阅的模型,可以观察该模型,以便在元素进入视口时得到通知。
创建一个观察者实例很简单,我们只需要创建一个IntersectionObserver的新实例并调用observe方法,传递一个DOM元素:
const observer = new IntersectionObserver();const coolElement = document.querySelector("#coolElement");observer.observe(coolElement);复制代码
接下来可以使用回调方式将参数传给InersectionObserver:
const observer = new IntersectionObserver(entries => { const firstEntry = entries[0]; if (firstEntry.isIntersecting) { // Handle intersection here... }});const coolDiv = document.querySelector("#coolDiv");observer.observe(coolDiv);复制代码
回调接收entries作为其参数。 这是一个数组,因为当你使用阈值时你可以有几个条目,但事实并非如此,所以只得到第一个元素。 然后可以使用firstEntry.isIntersection属性检查它是否相交。 这是进行异步请求并检索下一个页面的数据。
IntersectionObserver构造函数使用以下表示法接收选项组件作为其第二个参数:
const options = { root: document.querySelector("#scrollArea"), rootMargin: "0px", threshold: 1.0};const observer = new IntersectionObserver(callback, options);复制代码
关于options里的参数解释,截自
==root==:性指定目标元素所在的容器节点(即根元素)。注意,容器元素必须是目标元素的祖先节点
==rootMargin==: 定义根元素的margin,用来扩展或缩小rootBounds这个矩形的大小,从而影响intersectionRect交叉区域的大小。它使用CSS的定义方法,比如10px 20px 30px 40px,表示 top、right、bottom 和 left 四个方向的值。
这样设置以后,不管是窗口滚动或者容器内滚动,只要目标元素可见性变化,都会触发观察器
==threshold==:决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0],即交叉比例(intersectionRatio)达到0时触发回调函数。 比如,[0, 0.25, 0.5, 0.75, 1]
就表示当目标元素0%、25%、50%、75%、100%
可见时,会触发回调函数。
由于需要使用dom元素作为观察者,在Vue中,使用mounted,React中使用componentDidMount
// Observer.vueexport default { data: () => ({ observer: null }), mounted() { this.observer = new IntersectionObserver(([entry]) => { if (entry && entry.isIntersecting) { // ... } }); this.observer.observe(this.$el); }};复制代码
注意:我们在* [entry] *参数上使用数组解构,使用this.$el作为root以便观察
为了使其可重用,我们需要让父组件(使用Observer组件的组件)处理相交的事件。 为此,可以在它相交时发出一个自定义事件:
export default { mounted() { this.observer = new IntersectionObserver(([entry]) => { if (entry && entry.isIntersecting) { this.$emit("intersect"); } }); this.observer.observe(this.$el); } // ...}; 复制代码
组件销毁的时候,记得关闭observer
export default { destroyed() { this.observer.disconnect(); } // ...};复制代码
与==unobserve==不同的是,unobserve关闭当前被观察的元素,而disconnect关闭所有被观察的元素。
复制代码
创建无限滚动组件Vue
假如有如下类似需求
复制代码
- { {item.name}}
引入Observer组件
复制代码
- { {item.name}}
将==mounted==钩子里的异步请求移到==methods==里,并加上自增page以及合并items数据
export default { data: () => ({ page: 1, items: [] }), methods: { async intersected() { const res = await fetch( `https://jsonplaceholder.typicode.com/comments?_page=${ this.page }&_limit=50` ); this.page++; const items = await res.json(); this.items = [...this.items, ...items]; } }};复制代码
this.items = [...this.items, ...items] 等价于 this.items.concat(items)
到此InfiniteScroll.vue已经完成
复制代码
- { {item.name}}
值得注意的是,intersection Observer api兼容性并不是太好,经本人测试,chrome上无压力,其余全不兼容,不过可以使用[W3C’s Intersection Observer(https://github.com/w3c/IntersectionObserver/tree/master/polyfill),npm install intersection-observer
,然后在Observer.vue中加入require('intersection-observer');
即可。
Demo在此:https://codesandbox.io/s/kxm8wlnn85