可以先看一下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ù) ... ??
以下的目標(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ā)一次callback
,entries
為所有已監(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)了:
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
效果如下:
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í),效果如下:
當(dāng)所有盒子距離視窗頂部距離不一致
時(shí),效果如下:
為什么要舉例
以上兩種情況呢,因?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
效果如下:
實(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));
效果如下:
把網(wǎng)速調(diào)慢:
設(shè)置rootMargin
偏移值為"0px 0px -100px 0px
"(底部向內(nèi)收縮):
該方法還有一個(gè)好處,那就是當(dāng)頁面上某個(gè)節(jié)點(diǎn)存在橫向滾動(dòng)條的時(shí)候,一樣應(yīng)對(duì)自如:
傳統(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)聽參照元素
效果如下:
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);
效果如下:
但是有個(gè)問題,當(dāng)你滾動(dòng)的慢的時(shí)候,會(huì)掉進(jìn)一個(gè)死循環(huán):
為了方便觀察,我們把參考元素加一個(gè)高度跟顏色:
問題很明顯,當(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ì)影響參考元素
的位置:
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));
效果如下:
兼容性
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í)、一起交流、一起摸魚??