intersectionObserver API 介紹及實踐

背景

為了配合項目的一個前端曝光埋點功能堕阔,涉及到列表滾動绒窑,動態(tài)上報曝光行的數(shù)據(jù),進行了一個技術(shù)調(diào)研拷恨。
在前端開發(fā)工作中泣特,常常需要判斷某個元素是否進入了“視口”,一般的做法是監(jiān)聽滾動容器的滾動事件挑随,調(diào)用目標(biāo)元素位置方法状您,一般有兩種方式:

el.offesetTop - document.documentElement.scrollTop <= viewPortHeight 
el.getBoundingClientReact().top <= viewPortHeight

第一種:

function isInViewPortOfOne (el) {
    // viewPortHeight 兼容所有瀏覽器寫法
    const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight 
    const offsetTop = el.offsetTop
    const scrollTop = document.documentElement.scrollTop
    const top = offsetTop - scrollTop
    console.log('top', top)
     // 這里有個+100是為了提前加載+ 100
    return top <= viewPortHeight + 100
}

第二種:

function isInViewPortOfTwo (el) {
    const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight 
    const top = el.getBoundingClientRect() && el.getBoundingClientRect().top
    console.log('top', top)
    return top  <= viewPortHeight + 100
}

但是這種會造成頁面的重排勒叠,對性能影響很大。本身社招ATS列表已經(jīng)非常臃腫膏孟,且已經(jīng)有一些性能問題眯分,所以需要尋找性能更好的API來實現(xiàn)。
常見引起重排屬性和方法:

width     height     margin     padding     display     border     position     overflow     clientWidth     clientHeight 
clientTop     clientLeft     offsetWidth     offsetHeight     offsetTop     offsetLeft     scrollWidth     scrollHeight     scrollTop 
scrollLeft     scrollIntoView()     scrollTo()     getComputedStyle()     getBoundingClientRect()     scrollIntoViewIfNeeded()

介紹IntersectionObserver API

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

API
//初步用法 
var io = new IntersectionObserver(callback, option);

IntersectionObserver 是瀏覽器原生提供的構(gòu)造函數(shù)界逛,接受兩個參數(shù): callback是可見性變化時的回調(diào)函數(shù)昆稿,option是配置對象
構(gòu)造函數(shù)的返回值是一個觀察器實例。實例的observe方法可以指定觀察哪個 DOM 節(jié)點息拜。
observe的參數(shù)是一個 DOM 節(jié)點對象溉潭。如果要觀察多個節(jié)點,就要多次調(diào)用這個方法少欺。

// 開始觀察 
io.observe(document.getElementById('example')); 
// 停止觀察 
io.unobserve(element); 
// 關(guān)閉觀察器 
io.disconnect();
callback
var io = new IntersectionObserver( entries => { console.log(entries); } );

entries參數(shù)是一個數(shù)組喳瓣,每個被觀察的成員都是一個IntersectionObserverEntry對象,比如觀察了兩個元素赞别,那么entries的長度就是2
注意: 第一次調(diào)用new IntersectionObserver時畏陕,callback函數(shù)會先調(diào)用一次,即使元素未進入可視區(qū)域仿滔。

IntersectionObserverEntry

IntersectionObserverEntry對象提供目標(biāo)元素的信息蹭秋,一共有七個屬性
time:可見性發(fā)生變化的時間,是一個高精度時間戳堤撵,單位為毫秒
target:被觀察的目標(biāo)元素仁讨,是一個 DOM節(jié)點對象
rootBounds:根元素的矩形區(qū)域的信息,getBoundingClientRect()方法的返回值实昨,如果沒有根元素(即直接相對于視口滾動)洞豁,則返回null
boundingClientRect:目標(biāo)元素的矩形區(qū)域的信息
intersectionRect:目標(biāo)元素與視口(或根元素)的交叉區(qū)域的信息
intersectionRatio:目標(biāo)元素的可見比例,即intersectionRect占boundingClientRect的比例荒给,完全可見時為1丈挟,完全不可見時小于等于0
isIntersecting:一個布爾值,指示target元素是已轉(zhuǎn)換為相交狀態(tài)(true)還是已脫離相交狀態(tài)(false)志电。

Option

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

threshold

threshold屬性決定了什么時候觸發(fā)回調(diào)函數(shù)挑辆。它是一個數(shù)組例朱,每個成員都是一個門檻值孝情,默認(rèn)為[0],即交叉比例(intersectionRatio)達到0時(開始出現(xiàn)洒嗤、完全隱藏)觸發(fā)回調(diào)函數(shù)箫荡。
比如threshold設(shè)置為[0.5],這個時候當(dāng)元素第一次進入視口50%本身的面積的時候會調(diào)用一次渔隶,最后滾動出視口 50% 本身的面積的時候會再調(diào)用一次羔挡。
這個是為啥?
因為沒有取消observer间唉,如果說只想要調(diào)用一次绞灼,只要在觸發(fā)之后調(diào)用 unobserve 方法取消 observer就可以了。

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

用戶可以自定義這個數(shù)組呈野。比如低矮,[0, 0.25, 0.5, 0.75, 1]就表示當(dāng)目標(biāo)元素0%、25%际跪、50%商佛、75%喉钢、100%可見時姆打,會觸發(fā)回調(diào)函數(shù)。

root肠虽、rootMargin

很多時候幔戏,目標(biāo)元素不僅會隨著窗口滾動,還會在容器里面滾動(比如在iframe窗口里滾動)税课。容器內(nèi)滾動也會影響目標(biāo)元素的可見性
IntersectionObserver API支持容器內(nèi)滾動闲延。root屬性指定目標(biāo)元素所在的容器節(jié)點(即根元素)。注意韩玩,容器元素必須是目標(biāo)元素的祖先節(jié)點垒玲。
大白話來說,root 其實就是可以設(shè)置以哪個滾動元素為標(biāo)準(zhǔn)找颓,進行視口的計算合愈。

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桃纯、bottomleft 四個方向的值。 這樣設(shè)置以后辽狈,不管是窗口滾動或者容器內(nèi)滾動慈参,只要目標(biāo)元素可見性變化,都會觸發(fā)觀察器刮萌。

注意點

IntersectionObserver API是異步的驮配,不隨著目標(biāo)元素的滾動同步觸發(fā)。IntersectionObserver的實現(xiàn)着茸,應(yīng)該采用requestIdleCallback()壮锻,即只有線程空閑下來,才會執(zhí)行觀察器涮阔。這意味著猜绣,這個觀察器的優(yōu)先級非常低,只在其他任務(wù)執(zhí)行完敬特,瀏覽器有了空閑才會執(zhí)行掰邢。

關(guān)于 requestIdleCallback 可參考: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback

兼容性

https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver

polyfill

https://www.npmjs.com/package/intersection-observer

實踐

實例一:圖片懶加載

var url = ''; // 圖片鏈接
var io = new IntersectionObserver(
  entries => { 
    console.log(entries); 
    entries.forEach(item => {
      if(item.isIntersecting) {
        // 正常的圖片懶加載設(shè)計應(yīng)該是默認(rèn)加載一張縮略圖,然后等滾動到視口內(nèi)再替換 src 伟阔,這里直接用 innerHTML 方便辣之,只是演示使用
        item.target.innerHTML = `<img height="100%" src="${url}" />`;
        io.unobserve(item.target)
      }
    })
  }, {
    threshold: 1,
    root: document.querySelector('.container'),
  });
const boxList = document.querySelectorAll('.observer-item');
boxList.forEach(item => {
  io.observe(item);
})

實例二:無限滾動

var io = new IntersectionObserver(
  entries => { 
    console.log(entries); 
    entries.forEach(item => {
      if(item.isIntersecting) {
        loadItems();
      }
    })
  }, {
    threshold: 0.3,
    root: document.querySelector('.container'),
  });
io.observe(document.querySelector('.observer-target'));

function loadItems(){ /*加載新的items*/}

無限滾動時,最好在頁面底部有一個頁尾欄(又稱sentinels)皱炉。一旦頁尾欄可見怀估,就表示用戶到達了頁面底部,從而加載新的條目放在頁尾欄前面合搅。這樣做的好處是多搀,不需要再一次調(diào)用observe()方法,現(xiàn)有的IntersectionObserver可以保持使用灾部。
實例三:埋點曝光

const boxList = document.querySelectorAll('.mt-table-scroll .mt-table-row');
if(boxList.length <= 0) return;
var io = new IntersectionObserver((entries) =>{
  entries.forEach(item => {
    // 康铭。。赌髓。 埋點曝光代碼
    if (item.isIntersecting) {
       // do something
      }
      io.unobserve(item.target)
    }
  })
}, {
  root: null,
  threshold: 0.05, // 閥值設(shè)為0.05从藤,當(dāng)只有比例達到0.05時才觸發(fā)回調(diào)函數(shù)
})
// observe遍歷監(jiān)聽所有box節(jié)點
boxList.forEach(box => io.observe(box))
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市春弥,隨后出現(xiàn)的幾起案子呛哟,更是在濱河造成了極大的恐慌,老刑警劉巖匿沛,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扫责,死亡現(xiàn)場離奇詭異,居然都是意外死亡逃呼,警方通過查閱死者的電腦和手機鳖孤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門者娱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人苏揣,你說我怎么就攤上這事黄鳍。” “怎么了平匈?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵框沟,是天一觀的道長。 經(jīng)常有香客問我增炭,道長忍燥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任隙姿,我火速辦了婚禮梅垄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘输玷。我一直安慰自己队丝,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布欲鹏。 她就那樣靜靜地躺著机久,像睡著了一般。 火紅的嫁衣襯著肌膚如雪貌虾。 梳的紋絲不亂的頭發(fā)上吞加,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天裙犹,我揣著相機與錄音尽狠,去河邊找鬼。 笑死叶圃,一個胖子當(dāng)著我的面吹牛袄膏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掺冠,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼沉馆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了德崭?” 一聲冷哼從身側(cè)響起斥黑,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎眉厨,沒想到半個月后锌奴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡憾股,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年鹿蜀,在試婚紗的時候發(fā)現(xiàn)自己被綠了箕慧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡茴恰,死狀恐怖颠焦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情往枣,我是刑警寧澤伐庭,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站分冈,受9級特大地震影響似忧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丈秩,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一盯捌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蘑秽,春花似錦饺著、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缀雳,卻和暖如春渡嚣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肥印。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工识椰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人深碱。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓腹鹉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親敷硅。 傳聞我的和親對象是個殘疾皇子功咒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容