开发过程当中,常常会遇到须要处理大量数据的状况,好比列表、历史记录等,一般选择无限加载和分页导航。html
传统后端渲染,通常会选择分页导航,它能够轻松跳转,甚至一次跳转几个页面,如今SPA盛行,无限滚动加载是更好的方案,能够给用户更好的体验,尤为是在移动端。vue
在Awesome Vue中,有以下无限滚动组件ios
Intersection Observer API的出现,让开发无限滚动组件变得更加简单方便。git
Intersection Observer API提供了一个可订阅的模型,能够观察该模型,以便在元素进入视口时获得通知。github
建立一个观察者实例很简单,咱们只须要建立一个IntersectionObserver的新实例并调用observe方法,传递一个DOM元素:chrome
const observer = new IntersectionObserver(); const coolElement = document.querySelector("#coolElement"); observer.observe(coolElement);
接下来可使用回调方式将参数传给InersectionObserver:npm
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属性检查它是否相交。 这是进行异步请求并检索下一个页面的数据。json
IntersectionObserver构造函数使用如下表示法接收选项组件做为其第二个参数:后端
const options = { root: document.querySelector("#scrollArea"), rootMargin: "0px", threshold: 1.0 }; const observer = new IntersectionObserver(callback, options);
关于options里的参数解释,截自ruanyifeng intersectionobserver_apiapi
==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.vue export 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); } // ... }; <template> <div class="observer"/> </template>
组件销毁的时候,记得关闭observer
export default { destroyed() { this.observer.disconnect(); } // ... };
与==unobserve==不一样的是,unobserve关闭当前被观察的元素,而disconnect关闭全部被观察的元素。
<!-- Observer.vue --> <template> <div class="observer"/> </template> <script> export default { props: ['options'], data: () => ({ observer: null, }), mounted() { const options = this.options || {}; this.observer = new IntersectionObserver(([entry]) => { if (entry && entry.isIntersecting) { this.$emit("intersect"); } }, options); this.observer.observe(this.$el); }, destroyed() { this.observer.disconnect(); }, }; </script>
假若有以下相似需求
<template> <div> <ul> <li class="list-item" v-for="item in items" :key="item.id"> {{item.name}} </li> </ul> </div> </template> <script> export default { data: () => ({ page: 1, items: [] }), async mounted() { const res = await fetch( `https://jsonplaceholder.typicode.com/comments?_page=${ this.page }&_limit=50` ); this.items = await res.json(); } }; </script>
引入Observer组件
<template> <div> <ul> <li class="list-item" v-for="item in items" :key="item.id"> {{item.name}} </li> </ul> <Observer @intersect="intersected"/> </div> </template> <script> import Observer from "./Observer"; export default { data: () => ({ page: 1, items: [] }), async mounted() { const res = await fetch( `https://jsonplaceholder.typicode.com/comments?_page=${ this.page }&_limit=50` ); this.items = await res.json(); }, components: { Observer } }; </script>
将==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已经完成
<!-- InfiniteScroll.vue --> <template> <div> <ul> <li class="list-item" v-for="item in items" :key="item.id">{{item.name}}</li> </ul> <Observer @intersect="intersected"/> </div> </template> <script> import Observer from "./Observer"; 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]; }, }, components: { Observer, }, }; </script>
值得注意的是,intersection Observer api兼容性并非太好,经本人测试,chrome上无压力,其他全不兼容,不过可使用W3C’s Intersection Observer,npm install intersection-observer
,而后在Observer.vue中加入require('intersection-observer');
便可。