開發過程當中,常常會遇到須要處理大量數據的狀況,好比列表、歷史記錄等,一般選擇無限加載和分頁導航。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');
便可。