觀察者模式監聽判斷dom元素是否在可視區域內
本項目是使用vue3的寫法。
1.IntersectionObserver
IntersectionObserver
可以用來自動監聽元素是否進入了設備的可視區域之內,而不需要頻繁的計算來做這個判斷。由于可見(visible)的本質是,目標元素與視口產生一個交叉區
,所以這個 API 叫做"交叉觀察器
"
const observer = new IntersectionObserver(callback, option);
IntersectionObserver
是瀏覽器原生提供的構造函數,接受兩個參數:
- callback:可見性發現變化時的回調函數
- option:配置對象(可選)。
構造函數的返回值是一個觀察器實例。實例一共有4個方法:
- observe:開始監聽特定元素
- unobserve:停止監聽特定元素
- disconnect:關閉監聽工作
- takeRecords:返回所有觀察目標的對象數組
1.1 observe 方法
該方法需要接收一個target
參數,值是Element
類型,用來指定被監聽
的目標元素
// 獲取元素
const target = document.getElementById("dom");// 開始觀察
io.observe(target);
1.2 unobserve 方法
該方法需要接收一個target
參數,值是Element
類型,用來指定停止
監聽的目標元素
// 獲取元素
const target = document.getElementById("dom");// 停止觀察
io.unobserve(target);
1.3 disconnect 方法
該方法不需要接收參數,用來關閉觀察器
// 關閉觀察器
io.disconnect();
// 頁面加載時監聽元素
onMounted(() => {var demo3 = document.querySelector('document.querySelector(dom)) // 獲取元素var observer = new IntersectionObserver((mutaions)=>{ // 創建IntersectionObserver對象console.log(mutaions[0].isIntersecting)})observer.observe(demo3) // 需要監聽的元素
}
1.4 takeRecords 方法
該方法不需要接收參數,返回所有被觀察的對象,返回值是一個數組
// 獲取被觀察元素
const observerList = io.takeRecords();
1.5 callback 參數
目標元素的可見性變化時,就會調用觀察器的回調函數callback。
callback一般會觸發兩次。一次是目標元素剛剛進入視口,另一次是完全離開視口。
const io = new IntersectionObserver((changes, observer) => {console.log(changes);console.log(observer);
});
1.6 options
threshold:
決定了什么時候觸發回調函數。它是一個數組,每個成員都是一個門檻值,默認為[0],即交叉比例(intersectionRatio)達到0時觸發回調函數。用戶可以自定義這個數組。比如,[0, 0.25, 0.5, 0.75, 1]就表示當目標元素 0%、25%、50%、75%、100% 可見時,會觸發回調函數。root
: 用于觀察的根元素,默認是瀏覽器的視口,也可以指定具體元素,指定元素的時候用于觀察的元素必須是指定元素的子元素rootMargin
: 用來擴大或者縮小視窗的的大小,使用css的定義方法,10px 10px 30px 20px表示top、right、bottom 和 left的值
2. IntersectionObserverEntry 對象
changes數組中的每一項都是一個IntersectionObserverEntry 對象
boundingClientRect
:目標元素的矩形區域的信息- i
ntersectionRatio
:目標元素的可見比例,即intersectionRect占
boundingClientRect的比例,完全可見時為1,完全不可見時小于等于0 intersectionRect
:目標元素與視口(或根元素)的交叉區域的信息isIntersecting
: 布爾值,目標元素與交集觀察者的根節點是否相交(常用)isVisible
: 布爾值,目標元素是否可見(該屬性還在試驗階段,不建議在生產環境中使用)rootBounds
:根元素的矩形區域的信息,getBoundingClientRect()方法的返回值,如果沒有根元素(即直接相對于視口滾動),則返回nulltarget
:被觀察的目標元素,是一個 DOM 節點對象(常用)time
:可見性發生變化的時間,是一個高精度時間戳,單位為毫秒
3. 是否在可視區域
onMounted(() => {var observer = new IntersectionObserver((entries) => {console.log(111111, entries[0].isIntersecting);dataMap.showMyBox = !entries[0].isIntersecting; //返回true代表在頁面可視區域,false代表不在頁面可視區域。});observer.observe(document.querySelector(dom));
}
// 頁面卸載時可解綁
onBeforeUnmount(() => {if (observer) {observer.unobserve(document.querySelector(dom)); //解綁元素observer.disconnect(); //停止監聽}
});
4. 圖片懶加載
使用 IntersectionObserver 非常容易實現圖片懶加載,首先需要觀察懶加載元素,然后等元素進入可視區域后設置圖片 src;同時,還可以結合 IntersectionObserver.rootMargin
實現提前加載
圖片,一般可以設置為 1~2 倍瀏覽器窗口的視口高度,優化用戶體驗
/*** @method lazyLoad* @param {NodeList} $imgList 圖片元素集合* @param {number} preloadHeight 預加載高度*/
export function lazyLoad($imgList, preloadHeight = 1000) {const observer = new IntersectionObserver((entries) => {entries.forEach((entry) => {if (entry.isIntersecting) { // 目標元素出現在 root 可視區,返回 trueconst $target = entry.targetconst src = $target.getAttribute('lazyload')if (src) {$target.setAttribute('src', src) // 真正加載圖片}observer.unobserve($target) // 解除觀察}})}, {rootMargin: `0px 0px ${preloadHeight}px 0px`,})Array.prototype.forEach.call($imgList, ($item) => {if ($item.getAttribute('src')) return // 過濾已經加載過的圖片observer.observe($item) // 開始觀察})
}
使用方法:
// 圖片元素設置 lazyload 屬性
<img lazyload="圖片鏈接" alt="圖片說明">// 觀察圖片元素
lazyLoad(document.querySelectorAll("[lazyload]"))
5. 元素吸頂、吸底
如果頁面結構比較簡單可以直接使用 css 粘性布局。
IntersectionObserver 實現元素固定思路也很簡單,首先需要給固定元素包一層父元素,父元素指定高度占位,防止固定元素吸附時頁面抖動,然后觀察父元素的可視狀態變化,當父元素
即將離開可視區域
時改變固定元素的樣式。
/*** @method fixBanner* @param {HTMLElement} $observeEle 觀察元素* @param {HTMLElement} $fixEle 固定定位元素*/
export function fixBanner($observeEle, $fixEle) {const $ele = $fixEleconst observer = new IntersectionObserver((entries) => {entries.forEach((entry) => {if (entry.isIntersecting) {$ele.style.cssText = ''} else {$ele.style.cssText = 'position: fixed; top: 0; left: 0'}})}, {threshold: 1, // threshold 設置為 1 表示目標元素完全可見時觸發回調函數})observer.observe($observeEle) // 開始觀察
}
6. 加載更多
IntersectionObserver 實現加載更多需要在列表后面增加一個尾部元素(比如加載更多動畫),當尾部元素進入可視區域
就加載更多數據,注意尾部元素一定要一直處于所有列表元素的后面。
提前加載高度不能隨意設置,如果設置太大會導致尾部元素一直處于可視狀態。
function loadMore() {const observer = new IntersectionObserver((entries) => {const loadingEntry = entries[0]if (loadingEntry.isIntersecting) {// 請求數據并插入列表}},{rootMargin: '0px 0px 600px 0px', // 提前加載高度},)observer.observe(document.querySelector('.mod_loading')) // 觀察尾部元素
}