IntersectionObserver API 使用教程(懶加載,無限滾動)

網(wǎng)頁開發(fā)時,常常需要了解某個元素是否進入了"視口"(viewport),即用戶能不能看到它


image.png

上圖的綠色方塊不斷滾動,頂部會提示它的可見性。

傳統(tǒng)的實現(xiàn)方法是,監(jiān)聽到scroll事件后,調(diào)用目標元素(綠色方塊)的getBoundingClientRect()方法,得到它對應(yīng)于視口左上角的坐標,再判斷是否在視口之內(nèi)。這種方法的缺點是,由于scroll事件密集發(fā)生,計算量很大,容易造成性能問題。

目前有一個新的 IntersectionObserver API,可以自動"觀察"元素是否可見,Chrome 51+ 已經(jīng)支持。由于可見(visible)的本質(zhì)是,目標元素與視口產(chǎn)生一個交叉區(qū),所以這個 API 叫做"交叉觀察器"。

一、API

它的用法非常簡單。


var io = new IntersectionObserver(callback, option);

上面代碼中,IntersectionObserver是瀏覽器原生提供的構(gòu)造函數(shù),接受兩個參數(shù):callback是可見性變化時的回調(diào)函數(shù),option是配置對象(該參數(shù)可選)。

構(gòu)造函數(shù)的返回值是一個觀察器實例。實例的observe方法可以指定觀察哪個 DOM 節(jié)點。


// 開始觀察
io.observe(document.getElementById('example'));

// 停止觀察
io.unobserve(element);

// 關(guān)閉觀察器
io.disconnect();

上面代碼中,observe的參數(shù)是一個 DOM 節(jié)點對象。如果要觀察多個節(jié)點,就要多次調(diào)用這個方法。


io.observe(elementA);
io.observe(elementB);

二、callback 參數(shù)

目標元素的可見性變化時,就會調(diào)用觀察器的回調(diào)函數(shù)callback。

callback一般會觸發(fā)兩次。一次是目標元素剛剛進入視口(開始可見),另一次是完全離開視口(開始不可見)。


var io = new IntersectionObserver(
  entries => {
    console.log(entries);
  }
);

上面代碼中,回調(diào)函數(shù)采用的是箭頭函數(shù)的寫法。callback函數(shù)的參數(shù)(entries)是一個數(shù)組,每個成員都是一個IntersectionObserverEntry對象。舉例來說,如果同時有兩個被觀察的對象的可見性發(fā)生變化,entries數(shù)組就會有兩個成員。

三、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:可見性發(fā)生變化的時間,是一個高精度時間戳,單位為毫秒
  • target:被觀察的目標元素,是一個 DOM 節(jié)點對象
  • rootBounds:根元素的矩形區(qū)域的信息,getBoundingClientRect()方法的返回值,如果沒有根元素(即直接相對于視口滾動),則返回null
  • boundingClientRect:目標元素的矩形區(qū)域的信息
  • intersectionRect:目標元素與視口(或根元素)的交叉區(qū)域的信息
  • intersectionRatio:目標元素的可見比例,即intersectionRectboundingClientRect的比例,完全可見時為1,完全不可見時小于等于0
image

上圖中,灰色的水平方框代表視口,深紅色的區(qū)域代表四個被觀察的目標元素。它們各自的intersectionRatio圖中都已經(jīng)注明。

我寫了一個 Demo,演示IntersectionObserverEntry對象。注意,這個 Demo 只能在 Chrome 51+ 運行。

四、實例:惰性加載(lazy load)

有時,我們希望某些靜態(tài)資源(比如圖片),只有用戶向下滾動,它們進入視口時才加載,這樣可以節(jié)省帶寬,提高網(wǎng)頁性能。這就叫做"惰性加載"。

有了 IntersectionObserver API,實現(xiàn)起來就很容易了。


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);
});

上面代碼中,只有目標區(qū)域可見時,才會將模板內(nèi)容插入真實 DOM,從而引發(fā)靜態(tài)資源的加載。

五、實例:無限滾動

無限滾動(infinite scroll)的實現(xiàn)也很簡單。


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)。一旦頁尾欄可見,就表示用戶到達了頁面底部,從而加載新的條目放在頁尾欄前面。這樣做的好處是,不需要再一次調(diào)用observe()方法,現(xiàn)有的IntersectionObserver可以保持使用。

六、Option 對象

IntersectionObserver構(gòu)造函數(shù)的第二個參數(shù)是一個配置對象。它可以設(shè)置以下屬性。

6.1 threshold 屬性

threshold屬性決定了什么時候觸發(fā)回調(diào)函數(shù)。它是一個數(shù)組,每個成員都是一個門檻值,默認為[0],即交叉比例(intersectionRatio)達到0時觸發(fā)回調(diào)函數(shù)。


new IntersectionObserver(
  entries => {/* ... */}, 
  {
    threshold: [0, 0.25, 0.5, 0.75, 1]
  }
);

用戶可以自定義這個數(shù)組。比如,[0, 0.25, 0.5, 0.75, 1]就表示當目標元素 0%、25%、50%、75%、100% 可見時,會觸發(fā)回調(diào)函數(shù)。

image

6.2 root 屬性,rootMargin 屬性

很多時候,目標元素不僅會隨著窗口滾動,還會在容器里面滾動(比如在iframe窗口里滾動)。容器內(nèi)滾動也會影響目標元素的可見性,參見本文開始時的那張示意圖。

IntersectionObserver API 支持容器內(nèi)滾動。root屬性指定目標元素所在的容器節(jié)點(即根元素)。注意,容器元素必須是目標元素的祖先節(jié)點。


var opts = { 
  root: document.querySelector('.container'),
  rootMargin: "500px 0px" 
};

var observer = new IntersectionObserver(
  callback,
  opts
);

上面代碼中,除了root屬性,還有rootMargin屬性。后者定義根元素的margin,用來擴展或縮小rootBounds這個矩形的大小,從而影響intersectionRect交叉區(qū)域的大小。它使用CSS的定義方法,比如10px 20px 30px 40px,表示 top、right、bottom 和 left 四個方向的值。

這樣設(shè)置以后,不管是窗口滾動或者容器內(nèi)滾動,只要目標元素可見性變化,都會觸發(fā)觀察器。

七、注意點

IntersectionObserver API 是異步的,不隨著目標元素的滾動同步觸發(fā)。

規(guī)格寫明,IntersectionObserver的實現(xiàn),應(yīng)該采用requestIdleCallback(),即只有線程空閑下來,才會執(zhí)行觀察器。這意味著,這個觀察器的優(yōu)先級非常低,只在其他任務(wù)執(zhí)行完,瀏覽器有了空閑才會執(zhí)行。

http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容