網(wǎng)頁開發(fā)時井誉,常常需要了解某個元素是否進入了"視口"(viewport)蕉扮,即用戶能不能看到它。
上圖的綠色方塊不斷滾動颗圣,頂部會提示它的可見性喳钟。
傳統(tǒng)的實現(xiàn)方法是屁使,監(jiān)聽到scroll
事件后,調(diào)用目標元素(綠色方塊)的getBoundingClientRect()
方法奔则,得到它對應于視口左上角的坐標蛮寂,再判斷是否在視口之內(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
:目標元素的可見比例眼俊,即intersectionRect
占boundingClientRect
的比例意狠,完全可見時為1
,完全不可見時小于等于0
上圖中疮胖,灰色的水平方框代表視口环戈,深紅色的區(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ù)是一個配置對象西潘。它可以設置以下屬性卷玉。
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ù)附迷。
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 四個方向的值。
這樣設置以后绎橘,不管是窗口滾動或者容器內(nèi)滾動胁孙,只要目標元素可見性變化,都會觸發(fā)觀察器称鳞。
七涮较、注意點
IntersectionObserver API 是異步的,不隨著目標元素的滾動同步觸發(fā)冈止。
規(guī)格寫明狂票,IntersectionObserver
的實現(xiàn),應該采用requestIdleCallback()
熙暴,即只有線程空閑下來闺属,才會執(zhí)行觀察器。這意味著周霉,這個觀察器的優(yōu)先級非常低掂器,只在其他任務執(zhí)行完,瀏覽器有了空閑才會執(zhí)行俱箱。