瀏覽器的五種 Observer,你用過幾種?
網頁開發中我們經常要處理用戶交互,我們會用 addEventListener 添加事件監聽器來監聽各種用戶操作,比如 click、mousedown、mousemove、input 等,這些都是由用戶直接觸發的事件。
那么對于一些不是由用戶直接觸發的事件呢?比如元素從不可見到可見、元素大小的改變、元素的屬性和子節點的修改等,這類事件如何監聽呢?
瀏覽器提供了 5 種 Observer 來監聽這些變動:MutationObserver、IntersectionObserver、PerformanceObserver、ResizeObserver、ReportingObserver。
我們分別來看一下:
IntersectionObserver
一個元素從不可見到可見,從可見到不可見,這種變化如何監聽呢?
用 IntersectionObserver。
IntersectionObserver 可以監聽一個元素和可視區域相交部分的比例,然后在可視比例達到某個閾值的時候觸發回調。
我們準備兩個元素:
<div id="box1">BOX111</div>
<div id="box2">BOX222</div>
加上樣式:
#box1,#box2 {
width: 100px;
height: 100px;
background: blue;
color: #fff;
position: relative;
}
#box1 {
top: 500px;
}
#box2 {
top: 800px;
}
這兩個元素分別在 500 和 800 px 的高度,我們監聽它們的可見性的改變。
const intersectionObserver = new IntersectionObserver(
function (entries) {
console.log('info:');
entries.forEach(item => {
console.log(item.target, item.intersectionRatio)
})
}, {
threshold: [0.5, 1]
});
intersectionObserver.observe( document.querySelector('#box1'));
intersectionObserver.observe( document.querySelector('#box2'));
創建一個 IntersectionObserver 對象,監聽 box1 和 box2 兩個元素,當可見比例達到 0.5 和 1 的時候觸發回調。
瀏覽器跑一下:
可以看到元素 box1 和 box2 在可視范圍達到一半(0.5)和全部(1)的時候分別觸發了回調。
這有啥用?
這太有用了,我們在做一些數據采集的時候,希望知道某個元素是否是可見的,什么時候可見的,就可以用這個 api 來監聽,還有做圖片的懶加載的時候,可以當可視比例達到某個比例再觸發加載。
除了可以監聽元素可見性,還可以監聽元素的屬性和子節點的改變:
MutationObserver
監聽一個普通 JS 對象的變化,我們會用 Object.defineProperty 或者 Proxy:
而監聽元素的屬性和子節點的變化,我們可以用 MutationObserver:
MutationObserver 可以監聽對元素的屬性的修改、對它的子節點的增刪改。
我們準備這樣一個盒子:
<div id="box"><button>光</button></div>
加上樣式:
#box {
width: 100px;
height: 100px;
background: blue;
position: relative;
}
就是這樣的:
我們定時對它做下修改:
setTimeout(() => {
box.style.background = 'red';
},2000);
setTimeout(() => {
const dom = document.createElement('button');
dom.textContent = '東東東';
box.appendChild(dom);
},3000);
setTimeout(() => {
document.querySelectorAll('button')[0].remove();
},5000);
2s 的時候修改背景顏色為紅色,3s 的時候添加一個 button 的子元素,5s 的時候刪除第一個 button。
然后監聽它的變化:
const mutationObserver = new MutationObserver((mutationsList) => {
console.log(mutationsList)
});
mutationObserver.observe(box, {
attributes: true,
childList: true
});
創建一個 MutationObserver 對象,監聽這個盒子的屬性和子節點的變化。
瀏覽器跑一下:
可以看到在三次變化的時候都監聽到了并打印了一些信息:
第一次改變的是 attributes,屬性是 style:
第二次改變的是 childList,添加了一個節點:
第三次也是改變的 childList,刪除了一個節點:
都監聽到了!
這個可以用來做什么呢?比如文章水印被人通過 devtools 去掉了,那么就可以通過 MutationObserver 監聽這個變化,然后重新加上,讓水印去不掉。
當然,還有很多別的用途,這里只是介紹功能。
除了監聽元素的可見性、屬性和子節點的變化,還可以監聽大小變化:
ResizeObserver
窗口我們可以用 addEventListener 監聽 resize 事件,那元素呢?
元素可以用 ResizeObserver 監聽大小的改變,當 width、height 被修改時會觸發回調。
我們準備這樣一個元素:
<div id="box"></div>
添加樣式:
#box {
width: 100px;
height: 100px;
background: blue;
}
在 2s 的時候修改它的高度:
const box = document.querySelector('#box');
setTimeout(() => {
box.style.width = '200px';
}, 3000);
然后我們用 ResizeObserver 監聽它的變化:
const resizeObserver = new ResizeObserver(entries => {
console.log('當前大小', entries)
});
resizeObserver.observe(box);
在瀏覽器跑一下:
大小變化被監聽到了,看下打印的信息:
可以拿到元素和它的位置、尺寸。
這樣我們就實現了對元素的 resize 的監聽。
除了元素的大小、可見性、屬性子節點等變化的監聽外,還支持對 performance 錄制行為的監聽:
PerformanceObserver
瀏覽器提供了 performance 的 api 用于記錄一些時間點、某個時間段、資源加載的耗時等。
我們希望記錄了 performance 那就馬上上報,可是怎么知道啥時候會記錄 performance 數據呢?
用 PeformanceObserver。
PerformanceObserver 用于監聽記錄 performance 數據的行為,一旦記錄了就會觸發回調,這樣我們就可以在回調里把這些數據上報。
比如 performance 可以用 mark 方法記錄某個時間點:
performance.mark('registered-observer');
用 measure 方法記錄某個時間段:
performance.measure('button clicked', 'from', 'to');
后兩個個參數是時間點,不傳代表從開始到現在。
我們可以用 PerformanceObserver 監聽它們:
<html>
<body>
<button onclick="measureClick()">Measure</button>
<img src="https://p9-passport.byteacctimg.com/img/user-avatar/4e9e751e2b32fb8afbbf559a296ccbf2~300x300.image" />
<script>
const performanceObserver = new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
console.log(entry);// 上報
})
});
performanceObserver.observe({entryTypes: ['resource', 'mark', 'measure']});
performance.mark('registered-observer');
function measureClick() {
performance.measure('button clicked');
}
</script>
</body>
</html>
創建 PerformanceObserver 對象,監聽 mark(時間點)、measure(時間段)、resource(資源加載耗時) 這三種記錄時間的行為。
然后我們用 mark 記錄了某個時間點,點擊 button 的時候用 measure 記錄了某個時間段的數據,還加載了一個圖片。
當這些記錄行為發生的時候,希望能觸發回調,在里面可以上報。
我們在瀏覽器跑一下試試:
可以看到 mark 的時間點記錄、資源加載的耗時、點擊按鈕的 measure 時間段記錄都監聽到了。
分別打印了這三種記錄行為的數據:
mark:
圖片加載:
measure:
用了這些數據,就可以上報上去做性能分析了。
除了元素、performance 外,瀏覽器還有一個 reporting 的監聽:
ReportingObserver
當瀏覽器運行到過時(deprecation)的 api 的時候,會在控制臺打印一個過時的報告:
瀏覽器還會在一些情況下對網頁行為做一些干預(intervention),比如會把占用 cpu 太多的廣告的 iframe 刪掉:
會在網絡比較慢的時候把圖片替換為占位圖片,點擊才會加載:
這些干預都是瀏覽器做的,會在控制臺打印一個報告:
這些干預或者過時的 api 并不是報錯,所以不能用錯誤監聽的方式來拿到,但這些情況對網頁 app 來說可能也是很重要的:
比如我這個網頁就是為了展示廣告的,但瀏覽器一干預給我把廣告刪掉了,我卻不知道。如果我知道的話或許可以優化下 iframe。
比如我這個網頁的圖片很重要,結果瀏覽器一干預給我換成占位圖了,我卻不知道。如果我知道的話可能會優化下圖片大小。
所以自然也要監聽,所以瀏覽器提供了 ReportingObserver 的 api 用來監聽這些報告的打印,我們可以拿到這些報告然后上傳。
const reportingObserver = new ReportingObserver((reports, observer) => {
for (const report of reports) {
console.log(report.body);//上報
}
}, {types: ['intervention', 'deprecation']});
reportingObserver.observe();
ReportingObserver 可以監聽過時的 api、瀏覽器干預等報告等的打印,在回調里上報,這些是錯誤監聽無法監聽到但對了解網頁運行情況很有用的數據。
文中的代碼上傳到了 github:https://github.com/QuarkGluonPlasma/browser-api-exercize
總結
監聽用戶的交互行為,我們會用 addEventListener 來監聽 click、mousedown、keydown、input 等事件,但對于元素的變化、performance 的記錄、瀏覽器干預行為這些不是用戶交互的事件就要用 XxxObserver 的 api 了。
瀏覽器提供了這 5 種 Observer:
- IntersectionObserver:監聽元素可見性變化,常用來做元素顯示的數據采集、圖片的懶加載
- MutationObserver:監聽元素屬性和子節點變化,比如可以用來做去不掉的水印
- ResizeObserver:監聽元素大小變化
- 還有兩個與元素無關的:
- PerformanceObserver:監聽 performance 記錄的行為,來上報數據
- ReportingObserver:監聽過時的 api、瀏覽器的一些干預行為的報告,可以讓我們更全面的了解網頁 app 的運行情況
這些 api 相比 addEventListener 添加的交互事件來說用的比較少,但是在特定場景下都是很有用的。
瀏覽器的 5 種 Observer,你用過幾種呢?在什么情況下用到過呢?