(轉自http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html)javascript
網頁開發時,經常須要了解某個元素是否進入了"視口"(viewport),即用戶能不能看到它。html
上圖的綠色方塊不斷滾動,頂部會提示它的可見性。java
傳統的實現方法是,監聽到scroll
事件後,調用目標元素(綠色方塊)的getBoundingClientRect()
方法,獲得它對應於視口左上角的座標,再判斷是否在視口以內。這種方法的缺點是,因爲scroll
事件密集發生,計算量很大,容易形成性能問題。git
目前有一個新的 IntersectionObserver API,能夠自動"觀察"元素是否可見,Chrome 51+ 已經支持。因爲可見(visible)的本質是,目標元素與視口產生一個交叉區,因此這個 API 叫作"交叉觀察器"。es6
1、API
它的用法很是簡單。github
var io = new IntersectionObserver(callback, option);
上面代碼中,IntersectionObserver
是瀏覽器原生提供的構造函數,接受兩個參數:callback
是可見性變化時的回調函數,option
是配置對象(該參數可選)。web
構造函數的返回值是一個觀察器實例。實例的observe
方法能夠指定觀察哪一個 DOM 節點。api
// 開始觀察 io.observe(document.getElementById('example')); // 中止觀察 io.unobserve(element); // 關閉觀察器 io.disconnect();
上面代碼中,observe
的參數是一個 DOM 節點對象。若是要觀察多個節點,就要屢次調用這個方法。數組
io.observe(elementA); io.observe(elementB);
2、callback 參數
目標元素的可見性變化時,就會調用觀察器的回調函數callback
。瀏覽器
callback
通常會觸發兩次。一次是目標元素剛剛進入視口(開始可見),另外一次是徹底離開視口(開始不可見)。
var io = new IntersectionObserver( entries => { console.log(entries); } );
上面代碼中,回調函數採用的是箭頭函數的寫法。callback
函數的參數(entries
)是一個數組,每一個成員都是一個IntersectionObserverEntry
對象。舉例來講,若是同時有兩個被觀察的對象的可見性發生變化,entries
數組就會有兩個成員。
3、IntersectionObserverEntry 對象
IntersectionObserverEntry
對象提供目標元素的信息,一共有六個屬性。
{ time: 3893.92, rootBounds: ClientRect { bottom: 920, height: 1024, left: 0, right: 1024, top: 0, width: 920 }, boundingClientRect: ClientRect { // ... }, intersectionRect: ClientRect { // ... }, intersectionRatio: 0.54, target: element }
每一個屬性的含義以下。
time
:可見性發生變化的時間,是一個高精度時間戳,單位爲毫秒target
:被觀察的目標元素,是一個 DOM 節點對象rootBounds
:根元素的矩形區域的信息,getBoundingClientRect()
方法的返回值,若是沒有根元素(即直接相對於視口滾動),則返回null
boundingClientRect
:目標元素的矩形區域的信息intersectionRect
:目標元素與視口(或根元素)的交叉區域的信息intersectionRatio
:目標元素的可見比例,即intersectionRect
佔boundingClientRect
的比例,徹底可見時爲1
,徹底不可見時小於等於0
上圖中,灰色的水平方框表明視口,深紅色的區域表明四個被觀察的目標元素。它們各自的intersectionRatio
圖中都已經註明。
我寫了一個 Demo,演示IntersectionObserverEntry
對象。注意,這個 Demo 只能在 Chrome 51+ 運行。
4、實例:惰性加載(lazy load)
有時,咱們但願某些靜態資源(好比圖片),只有用戶向下滾動,它們進入視口時才加載,這樣能夠節省帶寬,提升網頁性能。這就叫作"惰性加載"。
有了 IntersectionObserver API,實現起來就很容易了。
function query(selector) { return Array.from(document.querySelectorAll(selector)); } var observer = new IntersectionObserver( function(changes) { changes.forEach(function(change) { var container = change.target; var content = container.querySelector('template').content; container.appendChild(content); observer.unobserve(container); }); } ); query('.lazy-loaded').forEach(function (item) { observer.observe(item); });
上面代碼中,只有目標區域可見時,纔會將模板內容插入真實 DOM,從而引起靜態資源的加載。
5、實例:無限滾動
無限滾動(infinite scroll)的實現也很簡單。
var intersectionObserver = new IntersectionObserver( function (entries) { // 若是不可見,就返回 if (entries[0].intersectionRatio <= 0) return; loadItems(10); console.log('Loaded new items'); }); // 開始觀察 intersectionObserver.observe( document.querySelector('.scrollerFooter') );
無限滾動時,最好在頁面底部有一個頁尾欄(又稱sentinels)。一旦頁尾欄可見,就表示用戶到達了頁面底部,從而加載新的條目放在頁尾欄前面。這樣作的好處是,不須要再一次調用observe()
方法,現有的IntersectionObserver
能夠保持使用。
6、Option 對象
IntersectionObserver
構造函數的第二個參數是一個配置對象。它能夠設置如下屬性。
6.1 threshold 屬性
threshold
屬性決定了何時觸發回調函數。它是一個數組,每一個成員都是一個門檻值,默認爲[0]
,即交叉比例(intersectionRatio
)達到0
時觸發回調函數。
new IntersectionObserver( entries => {/* ... */}, { threshold: [0, 0.25, 0.5, 0.75, 1] } );
用戶能夠自定義這個數組。好比,[0, 0.25, 0.5, 0.75, 1]
就表示當目標元素 0%、25%、50%、75%、100% 可見時,會觸發回調函數。
6.2 root 屬性,rootMargin 屬性
不少時候,目標元素不只會隨着窗口滾動,還會在容器裏面滾動(好比在iframe
窗口裏滾動)。容器內滾動也會影響目標元素的可見性,參見本文開始時的那張示意圖。
IntersectionObserver API 支持容器內滾動。root
屬性指定目標元素所在的容器節點(即根元素)。注意,容器元素必須是目標元素的祖先節點。
var opts = { root: document.querySelector('.container'), rootMargin: "500px 0px" }; var observer = new IntersectionObserver( callback, opts );
上面代碼中,除了root
屬性,還有rootMargin
屬性。後者定義根元素的margin
,用來擴展或縮小rootBounds
這個矩形的大小,從而影響intersectionRect
交叉區域的大小。它使用CSS的定義方法,好比10px 20px 30px 40px
,表示 top、right、bottom 和 left 四個方向的值。
這樣設置之後,不論是窗口滾動或者容器內滾動,只要目標元素可見性變化,都會觸發觀察器。
7、注意點
IntersectionObserver API 是異步的,不隨着目標元素的滾動同步觸發。
規格寫明,IntersectionObserver
的實現,應該採用requestIdleCallback()
,即只有線程空閒下來,纔會執行觀察器。這意味着,這個觀察器的優先級很是低,只在其餘任務執行完,瀏覽器有了空閒纔會執行。
8、參考連接
(完)