Intersection Observer API提供了一種異步觀察目標元素與祖先元素或頂級文檔viewport的交集中的變化的方法。這使得以往較難實現的功能,更加簡單,例如,監聽圖片元素,在適當的時候懶加載圖片。
例子
先看下,下面是一個簡單的小例子
<div id="e1" style="background-color:black;width:100%;height:500px;">
<ul style="color:white;">
<li>boundingClientRect:<span id="el-boundingClientRect"></span></li>
<li>intersectionRatio:<span id="el-intersectionRatio"></span></li>
<li>intersectionRect:<span id="el-intersectionRect"></span></li>
<li>isIntersecting:<span id="el-isIntersecting"></span></li>
<li>rootBounds:<span id="el-rootBounds"></span></li>
<li>time:<span id="el-time"></span></li>
</ul>
</div>
var observer = new IntersectionObserver((entries,observer) => {
// 我只監聽了一個對象
let entry = entries[0]
document.querySelector("#el-boundingClientRect").innerHTML = JSON.stringify(entry.boundingClientRect);
document.querySelector("#el-intersectionRatio").innerHTML = JSON.stringify(entry.intersectionRatio);
document.querySelector("#el-intersectionRect").innerHTML = JSON.stringify(entry.intersectionRect);
document.querySelector("#el-isIntersecting").innerHTML = JSON.stringify(entry.isIntersecting);
document.querySelector("#el-rootBounds").innerHTML = JSON.stringify(entry.rootBounds);
document.querySelector("#el-time").innerHTML = JSON.stringify(entry.time);
//document.querySelector("el-target").innerHTML = entry.target;
}, {
threshold : [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
});
observer.observe(document.querySelector("#e1"));
簡書上由于安全原因,不能植入js腳本,所以無法預覽,如果你希望預覽例子,請前往我的博客
構造器
通過例子,我們可以看到Intersection Observer需要通過構造器來創建,即new IntersectionObserver(callback[, options])
,參數有兩部分組成,一個必傳的回調函數以及一個可選的配置參數
回調Callback
Intersection Observer的翻譯就是“交點觀察”,因此,他的回調就成了重點。當觀察元素與根元素之間的交叉狀態發生變化時,它會將這部分信息反饋回來--通過回調告知
在回調函數里,我們會接受到兩個對象,發生狀態變化的元素集合以及監聽者本身(注意:創建時,會將所有被觀察元素的狀態傳遞過來),監聽者本身我們可以通過它增加或者減少監聽的元素或者銷毀自身(后續講),這里我們更關注觀察元素
function callback(entries,observer) {
entries -> 一系列被觀察的元素
observer -> 觀察者
}
entries
是一個 IntersectionObserverEntry
對象的數組,IntersectionObserverEntry
包換以下元素(來自MDN)
- boundingClientRect: 返回包含目標元素的邊界信息的DOMRectReadOnly. 邊界的計算方式與 Element.getBoundingClientRect() 相同
- intersectionRatio: 返回intersectionRect 與 boundingClientRect 的比例值
- intersectionRect: 返回一個 DOMRectReadOnly 用來描述根和目標元素的相交區域
- isIntersecting: 返回一個布爾值, 如果目標元素與交叉區域觀察者對象(intersection observer) 的根相交,則返回 true .如果返回 true, 則 IntersectionObserverEntry 描述了變換到交叉時的狀態; 如果返回 false, 那么可以由此判斷,變換是從交叉狀態到非交叉狀態
- rootBounds: 返回一個 DOMRectReadOnly 用來描述交叉區域觀察者(intersection observer)中的根
- target: 與根出現相交區域改變的元素 (Element)
- time: 返回一個記錄從 IntersectionObserver 的時間原點(time origin)到交叉被觸發的時間的時間戳(DOMHighResTimeStamp)
具體的值是多少,我們可以在最上面的例子中看到,需要注意傳遞過來的對象都是只讀(畢竟回調,只是通知你發生變化了)
參數options
我們可以通過 options 配置 IntersectionObserver,他包含以下幾項配置
- root: 監聽元素的祖先元素Element對象,其邊界盒將被視作視口。目標在根的可見區域的的任何不可見部分都會被視為不可見。默認情況下文檔視口會作為root
- rootMargin: 一個在計算交叉值時添加至根的邊界盒(bounding_box)中的一組偏移量,類型為字符串(string) ,可以有效的縮小或擴大根的判定范圍從而滿足計算需要。語法大致和CSS 中的margin 屬性等同。默認值是"0px 0px 0px 0px"。
- threshold: 規定了一個監聽目標與邊界盒交叉區域的比例值,可以是一個具體的數值或是一組0.0到1.0之間的數組。若指定值為0.0,則意味著監聽元素即使與根有1像素交叉,此元素也會被視為可見. 若指定值為1.0,則意味著整個元素都交叉時視為可見。閾值的默認值為0.0。
在上面的例子中,我未修改root與rootMargin,你可以將瀏覽器的窗口作為可見區域,threshold定義了一系列數組,意味著到達那些交叉比時觸發回調
屬性
在創建或者回調函數中,我們可以得到 IntersectionObserver 對象,他包含以下屬性:
- root: 所監聽對象的具體祖先元素(element)。如果未傳入值或值為null,則默認使用頂級文檔的視窗
- rootMargin: 計算交叉時添加到根(root)邊界盒bounding box的矩形偏移量, 可以有效的縮小或擴大根的判定范圍從而滿足計算需要。此屬性返回的值可能與調用構造函數時指定的值不同,因此可能需要更改該值,以匹配內部要求。所有的偏移量均可用像素(pixel)(px)或百分比(percentage)(%)來表達, 默認值為"0px 0px 0px 0px"
- thresholds: 一個包含閾值的列表, 按升序排列, 列表中的每個閾值都是監聽對象的交叉區域與邊界區域的比率。當監聽對象的任何閾值被越過時,都會生成一個通知(Notification)。如果構造器未傳入值, 則默認值為0
IntersectionObserver 的屬性也都是只讀,他在創建之后不支持修改
方法
IntersectionObserver 通過以下方法添加或者取消監聽元素
- IntersectionObserver.disconnect() 使IntersectionObserver對象停止監聽工作。
- IntersectionObserver.observe() 使IntersectionObserver開始監聽一個目標元素。
- IntersectionObserver.takeRecords() 返回所有觀察目標的IntersectionObserverEntry對象數組。
- IntersectionObserver.unobserve() 使IntersectionObserver停止監聽特定目標元素。
真實的例子
簡書上由于安全原因,不能植入js腳本,所以無法預覽,如果你希望預覽例子,請前往我的博客
懶加載
IntersectionObserver 可以很簡單的實現圖片懶加載,開頭就提到的,看下面很少的代碼,我們就實現了懶加載的功能(目前已經好像圖片懶加載框架使用它了,比如lozad.js)
<img id="e2-lazy" load-url="/images/avatar.jpg" />
var observer = new IntersectionObserver((entries,observer) => {
let entry = entries[0]
if (entry.isIntersecting) {
let el = entry.target;
el.src = el.getAttribute('load-url');
}
});
observer.observe(document.querySelector("#e2-lazy"));
TOC 自動定位
見我的博客(Cake與NexT主題)的側邊欄,它使用的就是IntersectionObserver。不過在使用時,也遇到了些問題。比如,IntersectionObserver 在瀏覽器中的優先級不高(高效,必然也有缺陷),如果你很快的移動,可能無法觸發
我們(NexT團隊)第一次嘗試時,并未考慮優先級的事情,我們根據標題元素的是否在瀏覽器窗口可見計算其toc的位置,但如果快速滑動,那么toc的定位就會錯亂
后續,我們調整,擴大根窗口的區域(與文章一樣長)的方式,那么我們只需要判斷是不是已經交叉即可,即便滑動太快也無需擔心
相關的代碼可以見:
- https://github.com/jiangtj/hexo-theme-cake/blob/9532ba695834a80bde883386efd2af8ffe6fe351/source/js/next-boot.js#L61-L134
- https://github.com/theme-next/hexo-theme-next/blob/ba019030a1293c80536fcb16777cc05a94305118/source/js/utils.js#L222-L300
- https://github.com/theme-next/hexo-theme-next/pull/1125
本文作者: Mr.J
本文鏈接: https://www.dnocm.com/articles/beechnut/intersection-observer-info/
版權聲明: 本博客所有文章除特別聲明外,均采用 BY-NC-SA 許可協議。轉載請注明出處!