IntersectionObserver:上篇 – 基本介紹及使用

IntersectionObserver:上篇-基本介紹及使用
IntersectionObserver:上篇-基本介紹及使用

IntersectionObserver?拿超商的歡迎光臨來說明

一開始知道有「IntersectionObserver」這個 Web API,是在一篇講圖片 lazy load 的文章上看到的。

以前我們要做圖片的延遲載入(lazy load),會用 onscroll 來監聽圖片那個標籤的 offset().Top 是不是怎麼樣又怎麼樣的計算後,確定圖片出現在視窗上了,才把圖片的路徑塞進 src 裡,以前 August 就寫過這樣:

$('selector').offset().top - $(window).scrollTop() <= $(window).height()

這個的缺點就是 onscroll,瀏覽器會在每一次的捲動都執行監聽的動作。

IntersectionObserver 這個 API,很純悴的就是指定的目標出現在觀察器(window)中時,就傳一個 true 來告知。

onscroll 跟 IntersectionObserver 有什麼不一樣呢?我們來用便利商店來舉例。

假設今天你是員工,老闆要求只要有客人進來,你都要喊「歡迎光臨」。

onscroll 指的就是,從此後你就一直盯著門口,確認路人A進來時就喊歡迎光臨。這樣沒什麼不對,你確實達成目標了,但你也要花心力一直盯著門口看,做其它事情也心驚膽跳的怕有人來。

那 IntersectionObserver 是什麼呢?就是自動門,有人進來時它就發出「叮咚(true)」告訴你有人進來了。那是不是你就可以安心的做其它事,自動門發出聲音時再喊就好?

換成網頁的角度來看,「你」就是瀏覽器,「客人」就是要延遲載入的 img,那只要聽到 IntersectionObserver 發出叮咚了,再來執行 img src 放上正確路徑這個函式就行。

本篇會筆記的分成上、下 2 篇。

上篇,就是本篇,是 August 用自己的方式理解 IntersectionObserver 的使用方式,以及相關參數說明。

下篇,預計提供幾個實用的範例,像是 lazy load、無限滾動、側邊欄固定等。

另外為了不讓 IntersectionObserver 這麼長的名稱干擾閱讀,以下簡稱「IO」


IO 使用 3 步驟

使用 IO 的步驟很簡單,就 3 步,如下:

  1. 建立觀察器(observer),觀察器要有個鏡頭來觀察,這個鏡頭就是 root,root 如果沒有指定,預設就是指 window
  2. 指定觀察器要觀察的獵物(entry)
  3. 當獵物 進入(true) / 離開(false) 觀察器視窗的範圍,觀察器就執行 callback

在寫這篇前看了幾篇教學文(推薦的三篇會附在文未),因為寫的是高手,所以沒有像 August 一樣需要一個完整的使用步驟來理解及記憶,痛定思痛,不希望看到這篇的你對 IO 一頭霧水,就先整理出這三個步驟,以下開始筆記這三個步驟的程式碼。


1 建立觀察器(observer)

建立觀察器很簡單,就一行:

var observer = new IntersectionObserver(callback, [option]);

callback 就是當獵物(entry)進入到觀察器的鏡頭(root)內時,要做什麼事的 function。

option 是選填,不填就是預設值,這一段 August 是直接用一個 console.log 來看會 log 出什麼來理解的,看到 console.log 出 option 的東西後就懂了:

option 就是調整觀察器的鏡頭用的。

option 總共有以下幾個值可以填:

{
  root: null,
  rootMargin: "0px 0px 0px 0px",
  threshold: [0]
}

root

這邊想像成觀察器的鏡頭比較好理解。當目標進入到鏡頭內就 callback,離開了鏡頭也 callback。root 可以設一個 element,比方寫:

root: document.getElementById('container');

root 的預設是 null,root 為 null 時就代表鏡頭就是你的視窗,就是螢幕正在看的區域。

rootMargin

4 個值分別代表上、右、下、左,這個是放大或縮小鏡頭的邊界用的。比方你的螢幕只有 13 寸,但你想讓鏡頭的範圍拉大到 15 寸,這樣就可以讓目標離不開你的視線,那就填 rootMargin 來拉大。

rootMargin 的預設值是 "0px 0px 0px 0px"

2019.10.28 補充:
rootMargin,實際寫 Demo 頁時,發現使用起來完全不是理解的這麼回事。
比方想把鏡頭往下移 2000px,寫了 2000px 0px 0px 0px 後,卻還是一樣目標一出現到視窗底部,就判斷 entry.isIntersecting === true 了。
Google 了一下,也有其他人有遇到這問題:IntersectionObserver rootMargin’s positive and negative values are not working,目前還沒看到有人回答。
所以關於 rootMargin 這點,如果有大大知道原因,敬請於回覆區中回覆。

2020.07.08 補充:
後來實測,rootMargin 要想像成是移動整個鏡頭。比方 top 的值寫了 -100px 往上移,那在 bottom 的部份要相對應的寫成 100px,rootMargin 就可以寫這樣:-100px 0px 100px 0px。實測這樣子寫是成功的。

threshold

指獵物本身出現了多少部份在你的鏡頭裡,而出現的部份到了指定的百分比後,都會執行 callback。這個直接看圖比較好懂:

綠色的方塊出現在視窗中的範圍不同,上面那一排紅底的字就會顯示不同文字。

threshold 的預設值是 [0],就是緣色方塊一接觸到視窗的邊就 callback,像上圖就是設成:[0, 0.25, 0.5, 0.75, 1],代表綠色方塊出現了 0%、25%、50%、75%、100% 這 5 個範圍後,都要執行一次 callback。

再看一次建立觀察器的 code:

var observer = new IntersectionObserver(callback, [option]);

option 上面介紹完了,callback 會寫在第 3 步,本段要再說明一小段,就是當 observer 這個變數被命出來後,代表觀察器建好了,它本身可以做 3 件事:

  1. 開始觀察某個獵物:observer.observe(el)
  2. 取消觀察某個獵物:observer.unobserve(el)
  3. 自爆,關掉這個觀察器:observer.disconnect()

2 指定觀察器要觀察的獵物(entry)

第一步把觀察器建出來,接著要開始找獵物來觀察。比方我們想觀察 <img id="img">,就可以寫:

function callback(entry) {}

var observer = new IntersectionObserver(callback);

var img = document.getElementById('img');

observer.observe(img);

那當然,也可以直接用個迴圈觀察所有圖片:

function callback(entry) {}

var observer = new IntersectionObserver(callback);

var all_img = document.querySelectorAll('img');
Array.prototype.forEach.call(all_img, function(img) {
  observer.observe(img);
});

3 當獵物 進入(true) / 離開(false) 觀察器視窗的範圍,觀察器就執行 callback

上面第 2 步的 code 可以看到, function callback(entry) 這邊,IO 的 callback 會帶一個參數進來,常看到命名為 entry 或 entries,之所以會用複數是因為這個參數會是一個陣列。

像第 2 步的 code,有觀察 1 張圖片的,或是觀察多張圖片的,callback 帶進來的都會是陣列,不管觀察的獵物是 1 個或多個。

我們來 console.log(entry) 看一下 entry 這個陣列有什麼:

[
    {
      // ReadOnly:目標元素的矩形區域的信息
      boundingClientRect: { /* ... */ },

      // 獵物的可見比例
      intersectionRatio: 1,

      // ReadOnly:獵物與root的交叉區域
      intersectionRect: { /* ... */ },

      // 是否出現在鏡頭(root))中
      isIntersecting: true,

      // ReadOnly:鏡頭(root)的資訊
      rootBounds: { /* ... */ },

      // 獵物本身
      target: 獵物的DOM節點
    }
  ]

這邊講 3 個比較常會用到的:intersectionRatio、isIntersecting、target。

intersectionRatio

獵物的可見比例,這跟 IO 的 threshold 很像,都是在看獵物出現在視窗中多少部份。不一樣的是 IO 是指定全部獵物,而 intersectionRect 是指能讀,不能改變,可以讀出每個獵物出現了多少部份,直接看圖比較好懂:

可以看到,鏡頭裡上面的出現了 75%,中間的出現了 100%,最下面的出現了 12%。

target

target 就是這個獵物在 DOM 上的節點,所以可以直接用 entry[0].target 來抓到它。

isIntersecting

這個就是最重要的,就是超商那個例子的自動門「叮咚」聲。

當獵物進入到鏡頭後,isIntersecting 會是 true,不在鏡頭內就是 false。

所以再拿圖片 lazy load 來當示範,就可以這樣寫:

function callback(entry) {
  if(entry[0].isIntersecting) {
    entry[0].target.src = entry[0].target.dataset.src;
    observer.unobserve(entry[0].target);
  }
}

var observer = new IntersectionObserver(callback);

var img = document.getElementById('img');

observer.observe(img);

entry[0].isIntersectingtrue 時就執行替換 img src 的動作,並且取消對這個獵物的觀察,這樣替換 src 的動作就只會執行這一次。


原始碼

IO 的 3 個步驟筆記完了,IO 也會使用了,這邊附上 August 整理過的程式碼,忘記的話以後可以直接看原始碼來找:


參考資源

本篇筆記主要看了 3 篇的教學文跟文件:


本篇主要是 IO 的基本使用,下一篇會開始實作幾個範例。

IntersectionObserver:下篇 – 實際應用 lazyload、進場效果、無限捲動

Summary
IntersectionObserver:上篇 - 基本介紹及使用
Article Name
IntersectionObserver:上篇 - 基本介紹及使用
Description
本篇大綱:IntersectionObserver?拿超商的歡迎光臨來說明。IO 使用 3 步驟:1 建立觀察器(observer)、2 指定觀察器要觀察的獵物(entry)、3 當獵物進入(true) / 離開(false) 觀察器視窗的範圍,觀察器就執行 callback。原始碼。參考資源。
Augustus
Let's Write
Let's Write
https://letswrite.tw/wp-content/uploads/2020/08/logo_512.jpg
訂閱
通知
guest

4 Comments
最舊
最新
Inline Feedbacks
看所有留言
Barney
Barney
4 年 之前

rootMargin 把鏡頭往下移的部分
寫法應該是

{
   rootMargin: "0px 0px 2000px 0px"  // 如同CSS的順序 上 右 下 左
}

我自己測試可是以達成鏡頭下移的效果的
請再測試看看是否有效
 
另外謝謝你整理的資訊
非常清楚明瞭

pili.app
pili.app
4 年 之前

我把 rootMargin 理解成把可視區域縮小。
比如上方 fixed 的 navbar 有 90px ,我就設 {rootMargin:’-90px 0px 0px 0px’}