利用"交叉觀察者"這個(gè)小寶貝兒,輕松實(shí)現(xiàn)懶加載、吸頂、觸底 ?

可以先看一下MDN中的介紹:

IntersectionObserver接口,提供了一種異步觀察目標(biāo)元素與其祖先元素或頂級(jí)文檔視窗(viewport)交叉狀態(tài)的方法,祖先元素與視窗(viewport)被稱為根(root);

直接進(jìn)入正題,IntersectionObserver 翻譯為 "交叉觀察者",它的任務(wù)就是監(jiān)聽目標(biāo)元素指定父元素(用戶可指定,默認(rèn)為viewport)是否在發(fā)生交叉行為,簡單理解就是監(jiān)聽目標(biāo)元素是否進(jìn)入或者離開了指定父元素的內(nèi)部(理解這句就行了,管他交不交叉呢),我好像在開車,但是你們沒有證據(jù) ... ??

image

以下的目標(biāo)元素簡稱為目標(biāo)指定父元素簡稱為父親交叉行為簡稱為交叉viewport簡稱為視窗 ??

下面會(huì)有動(dòng)圖介紹,先忍忍!

用法

1. 構(gòu)造函數(shù)

new IntersectionObserver(callback, options);

2. callback

發(fā)生交叉的回調(diào),接受一個(gè)entries參數(shù),返回當(dāng)前已監(jiān)聽并且發(fā)生了交叉目標(biāo)集合(后面會(huì)舉例說明為什么是"且發(fā)生了交叉"):

new IntersectionObserver(entries => {
  entries.forEach(item => console.log(item));
  // ...
});

我們看看item里面包含哪些常用屬性:

屬性 說明
boundingClientRect 空間信息
intersectionRatio 元素可見區(qū)域的占比
isIntersecting 字面理解為是否正在交叉,可用做判斷元素是否可見
target 目標(biāo)節(jié)點(diǎn),就跟event.target一樣

注意:頁面初始化的時(shí)候會(huì)觸發(fā)一次callbackentries所有已監(jiān)聽的目標(biāo)集合?

3. options

顧名思義,它是一個(gè)配置參數(shù),對(duì)象類型,非必填,常用屬性如下:

屬性 說明
root 指定父元素,默認(rèn)為視窗
rootMargin 觸發(fā)交叉的偏移值,默認(rèn)為"0px 0px 0px 0px"(上左下右,正數(shù)為向外擴(kuò)散,負(fù)數(shù)則向內(nèi)收縮)
new IntersectionObserver(callback, {
  root: document.querySelector("xx"),
  rootMargin: "0px 0px -100px 0px"
});

如果設(shè)置rootMargin為"20px 0px 30px 30px",那么元素未到達(dá)視窗時(shí),就已經(jīng)切換為可見狀態(tài)了:

image

4. 常用方法

名稱 說明 參數(shù)
observe 開始監(jiān)聽一個(gè)目標(biāo)元素 節(jié)點(diǎn)
unobserve 停止監(jiān)聽一個(gè)目標(biāo)元素 節(jié)點(diǎn)
takeRecords 返回所有監(jiān)聽的目標(biāo)元素集合
disconnect 停止所有監(jiān)聽

例子

1. 假設(shè)頁面上有一個(gè)class="box"的盒子且父元素為視窗

let box = document.querySelector(".box");

let observer = new IntersectionObserver(entries => {
  entries.forEach(item => {
    let tips = item.isIntersecting ? "進(jìn)入了父元素的內(nèi)部" : "離開了父元素的內(nèi)部";
    console.log(tips);
  });
});

observer.observe(box); // 監(jiān)聽一個(gè)box

效果如下:


image

2. 假設(shè)頁面上有多個(gè)class="box"的盒子且父元素為視窗

let box = document.querySelectorAll(".box");

let observer = new IntersectionObserver(entries => console.log(`發(fā)生交叉行為,目標(biāo)元素有${entries.length}個(gè)`));

box.forEach(item => observer.observe(item)); // 監(jiān)聽多個(gè)box

當(dāng)所有盒子距離視窗頂部距離一致時(shí),效果如下:

image

當(dāng)所有盒子距離視窗頂部距離不一致時(shí),效果如下:

image

為什么要舉例以上兩種情況呢,因?yàn)?code>entries是返回當(dāng)前已監(jiān)聽并且發(fā)生了交叉目標(biāo)集合,第一種情況,大家都一起發(fā)生交叉,固每次返回的集合長度都為;第二種情況則是每個(gè)目標(biāo)輪流發(fā)生交叉,且當(dāng)前只觸發(fā)了一個(gè),所以每次返回的集合長度只有?

3. 指定父元素

假設(shè)html如下:

<div class="parent">
  <div class="child"></div>
</div>

然后開始監(jiān)聽:

let child = document.querySelector(".child");

let observer = new IntersectionObserver(entries => {
  entries.forEach(item => {
    console.log(item.isIntersecting ? "可見" : "不可見");
  });
}, {
  root: document.querySelector(".parent")
});

observer.observe(child); // 開始監(jiān)聽child

效果如下:


image

實(shí)際應(yīng)用

1. 圖片懶加載

以前都是監(jiān)聽瀏覽器滾動(dòng),然后遍歷拿到每個(gè)圖片的空間信息,然后判斷一些位置信息從而進(jìn)行圖片加載;而現(xiàn)在只需要交給交叉觀察者去做:

let images = document.querySelectorAll("img.lazyload");

let observer = new IntersectionObserver(entries => {
  entries.forEach(item => {
    if (item.isIntersecting) {
      item.target.src = item.target.dataset.origin; // 開始加載圖片
      observer.unobserve(item.target); // 停止監(jiān)聽已開始加載的圖片
    }
  });
});

images.forEach(item => observer.observe(item));

效果如下:


image

把網(wǎng)速調(diào)慢:


image

設(shè)置rootMargin偏移值為"0px 0px -100px 0px"(底部向內(nèi)收縮):

image

該方法還有一個(gè)好處,那就是當(dāng)頁面上某個(gè)節(jié)點(diǎn)存在橫向滾動(dòng)條的時(shí)候,一樣應(yīng)對(duì)自如:


image

傳統(tǒng)的懶加載只是監(jiān)聽全局滾動(dòng)條的滾動(dòng),像這種小細(xì)節(jié)還是無法實(shí)現(xiàn)的(傳統(tǒng)的實(shí)現(xiàn)方法并不是判斷目標(biāo)是否出現(xiàn)在視窗,所以橫向的圖片會(huì)一起加載,即使你沒有向左滑動(dòng)),所以這也是交叉觀察者的一大優(yōu)點(diǎn)?

2. 觸底

我們?cè)诹斜淼撞糠乓粋€(gè)參照元素,然后讓交叉觀察者去監(jiān)聽;

假設(shè)html結(jié)構(gòu)如下:

<!-- 數(shù)據(jù)列表 -->
<ul>
  <li>index</li>
</ul>

<!-- 參照元素 -->
<div class="reference"></div>

然后監(jiān)聽參照元素:

new IntersectionObserver(entries => {
  let item = entries[0]; // 拿第一個(gè)就行,反正只有一個(gè)
  if (item.isIntersecting) console.log("滾動(dòng)到了底部,開始請(qǐng)求數(shù)據(jù)");
}).observe(document.querySelector(".reference")); // 監(jiān)聽參照元素

效果如下:


image

3. 吸頂

實(shí)現(xiàn)元素吸頂?shù)姆绞接泻芏喾N,如css的position: sticky,兼容性較差;如果用交叉觀察者實(shí)現(xiàn)也很方便,同樣也要放一個(gè)參照元素

假設(shè)html結(jié)構(gòu)如下:

<!-- 參照元素 -->
<div class="reference"></div>

<nav>我可以吸頂</nav>

假設(shè)scss代碼如下:

nav {
  &.fixed {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
  }
}

開始監(jiān)聽:

let nav = document.querySelector('nav');
let reference = document.querySelector(".reference");

new IntersectionObserver(entries => {

  let item = entries[0];
  let top = item.boundingClientRect.top;

  // 當(dāng)參照元素的的top值小于0,也就是在視窗的頂部的時(shí)候,開始吸頂,否則移除吸頂
  if (top < 0) nav.classList.add("fixed");
  else nav.classList.remove("fixed");

}).observe(reference);

效果如下:


image

但是有個(gè)問題,當(dāng)你滾動(dòng)的慢的時(shí)候,會(huì)掉進(jìn)一個(gè)死循環(huán):


image

為了方便觀察,我們把參考元素加一個(gè)高度跟顏色:


image

問題很明顯,當(dāng)給nav增加fixed定位時(shí),nav脫離了文檔流,自然參考元素會(huì)往下掉,然后往下掉又發(fā)生了交叉,從而去除fixed定位,陷入一個(gè)死循環(huán);

思考了一會(huì),解決辦法是,讓參考元素絕對(duì)定位至nav的上方:

let nav = document.querySelector('nav');
let reference = document.querySelector(".reference");

reference.style.top = nav.offsetTop + "px";

// 以下代碼不變 ...

這樣,即使nav脫離的文檔流,也不會(huì)影響參考元素的位置:

image

4. 動(dòng)畫展示

相信很多人都需要過這種需求,當(dāng)某個(gè)元素出現(xiàn)的時(shí)候就給該元素加個(gè)動(dòng)畫,比如漸變、偏移等;

假設(shè)html結(jié)構(gòu)如下:

<ul>
  <li></li>
</ul>

假設(shè)scss代碼如下:

ul {
 li {
   &.show {
    // 默認(rèn)從左邊進(jìn)來
    animation: left 1s ease;
    
    // 偶數(shù)從右邊進(jìn)來
    &:nth-child(2n) {
      animation: right 1s ease;
    }
   }
 }
}

@keyframes left {
  from {
    opacity: 0;
    transform: translate(-20px, 20px); // right動(dòng)畫改成20px, 20px即可
  }

  to {
    opacity: 1;
  }
}

然后開始監(jiān)聽:

let list = document.querySelectorAll("ul li");

let observer = new IntersectionObserver(entries => {
  entries.forEach(item => {
    if (item.isIntersecting) {
      item.target.classList.add("show"); // 增加show類名
      observer.unobserve(item.target); // 移除監(jiān)聽
    }
  });
});

list.forEach(item => observer.observe(item));

效果如下:


image

兼容性

IE不兼容,不過有官方的polyfill?

最后

暫時(shí)就發(fā)現(xiàn)這么多用途啦,值得注意的是,必須是子元素跟父元素發(fā)生交叉,如果你想檢查兩個(gè)非父子關(guān)系的交叉,那是不行的嘻嘻,如果你覺得這篇文章不錯(cuò),請(qǐng)別忘記點(diǎn)個(gè)關(guān)注哦~??

交流

公眾號(hào)「前端宇宙情報(bào)局」,將不定時(shí)更新最新、實(shí)用的前端技巧/技術(shù)性文章,對(duì)了偶爾還會(huì)有互聯(lián)網(wǎng)中的趣事趣聞??

關(guān)注公眾號(hào),回復(fù)"1"獲取微信群聊二維碼,一起學(xué)習(xí)、一起交流、一起摸魚??

image

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,143評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,416評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,940評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,170評(píng)論 0 287
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,709評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,597評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,784評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評(píng)論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,029評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評(píng)論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,403評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,746評(píng)論 2 370

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