JavaScript監(jiān)聽DOM尺寸變化的解決方案
遠古方案:resize的弊端
- 全局監(jiān)聽,粒度粗糙
window.resize是全局事件,任何窗口尺寸變化都會觸發(fā)回調(diào),即使頁面中目標元素并未發(fā)生實際變化。開發(fā)者需手動遍歷并比對所有關(guān)注元素的尺寸,邏輯冗余且效率低下。 - 頻繁觸發(fā),易導(dǎo)致性能瓶頸 用戶拖拽調(diào)整窗口時,
resize事件會高頻連續(xù)觸發(fā),若回調(diào)中包含復(fù)雜計算或 DOM 操作,極易引發(fā)頁面卡頓。雖可通過節(jié)流(throttle)優(yōu)化,但仍無法從根本上解決問題。 - 無法感知非窗口引起的元素變化 當元素因內(nèi)容更新、CSS 動畫、父容器結(jié)構(gòu)調(diào)整等非窗口 resize 行為導(dǎo)致尺寸變化時,該事件完全無法捕獲,存在嚴重的監(jiān)聽盲區(qū)。
下面這段代碼,是舊版本的vben-admin封裝的echarts的hooks,使用了監(jiān)聽window.resize的方式監(jiān)聽屏幕尺寸變化,從而去重新渲染echarts圖表:
function initCharts(t = theme) {
const el = unref(elRef);
if (!el || !unref(el)) {
return;
}
chartInstance = echarts.init(el, t);
const { removeEvent } = useEventListener({
el: window,
name: 'resize',
listener: resizeFn,
});
removeResizeFn = removeEvent;
const { widthRef, screenEnum } = useBreakpoint();
if (unref(widthRef) <= screenEnum.MD || el.offsetHeight === 0) {
useTimeoutFn(() => {
resizeFn();
}, 30);
}
}
可見大部分人的第一想法還是用resize這個深入人心的方案。
完善的第三方庫——element-resize-detector
element-resize-detector 的底層原理是通過在目標元素內(nèi)部創(chuàng)建隱藏的 <object> 或監(jiān)聽滾動事件來間接檢測尺寸變化,從而規(guī)避瀏覽器無法原生監(jiān)聽元素 resize 的限制。
使用方式:
import elementResizeDetectorMaker from "element-resize-detector";
const erd = elementResizeDetectorMaker();
const chartContainer = document.getElementById("chartContainer");
// 模擬圖表繪制函數(shù)
function renderChart(width, height) {
console.log(`重新繪制圖表:寬${width}px,高${height}px`);
// 實際項目中此處可調(diào)用ECharts、HighCharts等圖表庫的重繪方法
}
// 監(jiān)聽元素尺寸變化 回調(diào)函數(shù)里重繪
erd.listenTo(chartContainer, function(element) {
const width = element.offsetWidth;
const height = element.offsetHeight;
renderChart(width, height);
})
具體實現(xiàn)依賴兩種核心策略:
- 滾動(scroll)策略(默認) 在目標元素內(nèi)動態(tài)插入一個隱藏的
<div>,并設(shè)置其寬高略大于父元素,同時監(jiān)聽該元素的scroll事件。當目標元素尺寸發(fā)生變化時,內(nèi)部元素的可滾動區(qū)域隨之改變,從而觸發(fā)scroll事件,實現(xiàn)對尺寸變化的捕獲。該方式性能高、兼容性好,適用于絕大多數(shù)現(xiàn)代瀏覽器。
具體步驟:
- 初始化監(jiān)聽事件和初始的尺寸
- 注入滾動元素
- 注冊監(jiān)聽:
registerListenersAndPositionElements里監(jiān)聽了渲染、收縮、擴展事件
getState(element).onRendered = handleRender; getState(element).onExpand = handleScroll; getState(element).onShrink = handleScroll;
function install() {
initListeners();
storeStartSize();
batchProcessor.add(0, storeStyle);
batchProcessor.add(1, injectScrollElements);
batchProcessor.add(2, registerListenersAndPositionElements);
batchProcessor.add(3, finalizeDomMutation);
batchProcessor.add(4, ready);
}
對象(object)策略
將一個隱藏的 <object> 標簽嵌入目標元素內(nèi)部,并指向當前頁面。<object> 元素會自動填充其容器,當容器尺寸變化時,<object> 會觸發(fā)自身的 resize 事件。由于 <object> 支持監(jiān)聽 resize,因此可借此間接感知父元素的尺寸變化。此方法主要用于兼容老舊瀏覽器(如 IE)。

核心邏輯:
// IE8 直接給元素綁定resize事件
if(browserDetector.isIE(8)) {
//IE 8 does not support object, but supports the resize event directly on elements.
getState(element).object = {
proxy: listenerProxy
};
element.attachEvent("onresize", listenerProxy);
} else {
var object = getObject(element);
if(!object) {
throw new Error("Element is not detectable by this strategy.");
}
object.contentDocument.defaultView.addEventListener("resize", listenerProxy);
}
對比:
| 策略名稱 | 優(yōu)點 | 缺點 |
|---|---|---|
| object | 精確性高 | 性能相對差,尤其是同一個頁面有多個元素需要監(jiān)聽時 |
| scroll | 性能較好 | 精確性較低、特定布局無法使用(源碼中有大量的兼容性處理) |
現(xiàn)代瀏覽器的處理方案——resizeObserver——以及簡單的封裝
ResizeObserver 是現(xiàn)代瀏覽器提供的原生 API,用于監(jiān)聽 DOM 元素內(nèi)容區(qū)域的尺寸變化,無需依賴輪詢或事件冒泡,具有高精度、高性能的特點。
精準監(jiān)聽元素級尺寸變化
ResizeObserver 能直接監(jiān)聽任意 DOM 元素的 content-box 或 border-box 尺寸變化,包括由 CSS 動畫、內(nèi)容更新、布局重排等引起的改變,無需手動計算或依賴窗口 resize 事件。
異步回調(diào),避免性能瓶頸
回調(diào)函數(shù)通過 requestAnimationFrame 異步執(zhí)行,不會阻塞渲染線程,即使在高頻變化場景下也能保持頁面流暢。
自動忽略不可見元素
對于 display: none 或未插入文檔的元素,ResizeObserver 會自動暫停監(jiān)聽,減少無效計算。
支持批量處理與資源清理
observe(element):開始監(jiān)聽指定元素。unobserve(element):停止監(jiān)聽某個元素。disconnect():斷開所有監(jiān)聽,釋放資源。
使用方式:
const observer = new ResizeObserver((entries) => {
entries.forEach((entry) => {
const { width, height } = entry.contentRect; // content-box 尺寸
console.log(`內(nèi)容區(qū)域尺寸:${width}x${height}`);
});
});
// 開始監(jiān)聽
observer.observe(document.getElementById('target'));
兼容性問題:
resizeObserver的瀏覽器支持情況如下:
Chrome: 從版本 64 開始支持(2018 年 1 月發(fā)布)
Firefox: 從版本 69 開始支持(2019 年 9 月發(fā)布)
Edge: 從版本 79 開始支持(2020 年 1 月發(fā)布)
Safari: 從版本 13.1 開始支持(2020 年 3 月發(fā)布)
Opera: 從版本 51 開始支持(2018 年 1 月發(fā)布)。
若需要支持更低版本的瀏覽器,需要引用polyfill文件。
簡單的封裝:
import ResizeObserver from 'resize-observer-polyfill';
function resizeHandler(entries: any[]) {
for (const entry of entries) {
const listeners = entry.target.__resizeListeners__ || [];
if (listeners.length) {
listeners.forEach((fn: () => any) => {
fn();
});
}
}
}
export function addResizeListener(element: any, fn: () => any) {
if (!element.__resizeListeners__) {
element.__resizeListeners__ = [];
element.__ro__ = new ResizeObserver(resizeHandler);
element.__ro__.observe(element);
}
element.__resizeListeners__.push(fn);
}
export function removeResizeListener(element: any, fn: () => any) {
if (!element || !element.__resizeListeners__) return;
element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
if (!element.__resizeListeners__.length) {
element.__ro__.disconnect();
}
}
上面的封裝是為了處理同一個元素多次監(jiān)聽,避免造成監(jiān)聽的覆蓋。封裝之后,用法和addEventListener比較相似,只是無需傳入事件類型。
使用方式:
const chartContainer = document.getElementById("chartContainer");
// 模擬圖表繪制函數(shù)
function renderChart() {
console.log(`重新繪制圖表`);
// 實際項目中此處可調(diào)用ECharts、HighCharts等圖表庫的重繪方法
}
// 監(jiān)聽元素尺寸變化 回調(diào)函數(shù)里重繪
addResizeListener(chartContainer, function() {
renderChart()
})
以上就是JavaScript監(jiān)聽DOM尺寸變化的解決方案的詳細內(nèi)容,更多關(guān)于JavaScript監(jiān)聽DOM尺寸變化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript程序員應(yīng)該知道的45個實用技巧
在這篇文章中,我將分享一組JavaScript的技巧、竅門和最佳實踐,這些都是JavaScript程序員應(yīng)該知曉的,不管他們是使用在瀏覽器/引擎上,還是服務(wù)器端(SSJS——Service Side JavaScript)JavaScript解釋器上2014-03-03
JavaScript關(guān)于提高網(wǎng)站性能的幾點建議(一)
這篇文章主要介紹了JavaScript關(guān)于提高網(wǎng)站性能的幾點建議(一)的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-07-07
在TypeScript中將unknown類型轉(zhuǎn)換為更具體的類型
本文介紹了TypeScript中unknown類型的使用,以及如何將unknown類型安全地轉(zhuǎn)換為更具體的類型,本文詳細介紹了類型斷言、內(nèi)置類型守衛(wèi)和自定義類型守衛(wèi)的使用方法,并提供了最佳實踐建議,以確保代碼的類型安全性和可維護性,需要的朋友可以參考下2026-04-04
詳解JavaScript實現(xiàn)動態(tài)的輪播圖效果
這篇文章主要介紹了JavaScript實現(xiàn)動態(tài)的輪播圖效果,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04

