我們都知道瀏覽器是沒有原生的 DOMResize 事件的凸丸,唯一能監(jiān)測到 Resize 事件的對象就是 window 這個全局對象茸时,但是這個事件只有在窗口大小改變的時候才會觸發(fā)润歉。當我們需要在 DOM 元素尺寸變化時想做一些事情的話就顯得無能為力了马澈,那么我們今天就來聊一聊如何手動實現(xiàn)這個功能罚随,其實也就是在給 DOM 元素增加一個 Resize 事件偵聽器的 polyfill壤靶。
DOM 事件是怎么觸發(fā)的
在實現(xiàn)這個功能之前我們先回顧一下平時給瀏覽器 DOM 注冊各種各樣的事件是怎么觸發(fā)的呢?如果我們把這個問題搞明白了线梗,這對接下來要實現(xiàn)的功能就能打開一個很好的思路椰于。
原生的 DOM 事件是瀏覽器響應用戶的交互行為,然后內(nèi)部會自動派發(fā)這個事件仪搔,當我們給 DOM 注冊的事件被瀏覽器派發(fā)是瘾婿,先前綁定的回調函數(shù)就會執(zhí)行了。首先注冊一個 DOM 事件是很容易的烤咧,就是我們經(jīng)常寫的代碼
document.querySelector('selector').addEventListener('resize', handler)
理想的情況是當我們改變元素尺寸是偏陪,注冊偵聽器函數(shù)的第二個參數(shù) handler 就會被執(zhí)行,當然這也是我們這篇文章最終要實現(xiàn)的目標煮嫌。
如何手動創(chuàng)建一個 DOM 事件
如果我們能自己手動創(chuàng)建一個 DOM 事件笛谦,然后在需要被觸發(fā)的時候去派發(fā)它,這樣我們的目的就達到了昌阿。當然這就要求我們對瀏覽器事件的相關 Api 有一定的了解饥脑,讓我們直接來看下面的代碼吧。
const event = document.createEvent('HTMLEvents')
event.initEvent('resize')
這樣簡單的兩行代碼就創(chuàng)建了一個自定義 DOM 事件宝泵,至于代碼中兩個函數(shù)的可傳參數(shù)都有哪些可以去翻閱文檔具體了解好啰,本文就不在贅述了。
觀測 DOM 元素尺寸變化(功能實現(xiàn)的核心)
我們有了自定義的事件儿奶,至于要我們在適當?shù)臅r候派發(fā)它就可以實現(xiàn)注冊函數(shù)的自動回調框往。那么這個事件派發(fā)的時機我們又要怎么完成。這就引出了本文的重點:如何觀測 DOM 元素的尺寸變化闯捎?幸好我們使用的現(xiàn)代瀏覽器提供了一個觀察元素尺寸變化的 Api(ResizeObserver),有個這個接口椰弊,我們就可以進行代碼實現(xiàn)了
const observer = new ResizeObserver(function elResizeChange(entries) {
// 每次被觀測的元素尺寸發(fā)生改變這里都會執(zhí)行
})
observer.observe(el) // 觀測DOM元素
從代碼里我們可以看到许溅,我們先是以構造器的方式創(chuàng)建了一個 observer 實例,然后又調用了實例的一個方法去觀測元素秉版,這樣我們就實現(xiàn)了一個 DOM 元素的尺寸觀測方法贤重。接著我們要說到傳遞到構造器里面的那個回調函數(shù)的作用了,其主要功能都是在這個函數(shù)里實現(xiàn)的清焕。這個函數(shù)會在被觀測元素尺寸變化的時候被調用并蝗,接下來我們也會在這里去實現(xiàn)事件的派發(fā)功能
事件的派發(fā)
接著上面已經(jīng)實現(xiàn)的代碼我們繼續(xù)來實現(xiàn) DOM 自定義事件的派發(fā)功能,這也是實現(xiàn) Resize 事件的最后一步秸妥,等實現(xiàn)了這一部滚停,Resize 事件的實現(xiàn)就大功告成了!同樣讓我們直接來看代碼吧
// 這里的代碼截取自上面的回調函數(shù)
function elResizeChange(entries) {
// entries 是個數(shù)組 每個成員又是一個ResizeObserverEntry 實例
for (let entry of entries) {
const event = document.createEvent('HTMLEvents') //創(chuàng)建DOM事件
event.initEvent('resize') // 初始化事件類型為resize
entry.target.dispatchEvent(event) // 事件派發(fā)
}
}
這里的 elResizeChange 函數(shù)就是我們創(chuàng)建元素尺寸觀察者實例時傳入的回調函數(shù),在這里我們實現(xiàn)了元素自定義事件的派發(fā)粥惧。我們在重點講一下這個函數(shù)主要實現(xiàn)的功能键畴。
因為一個 observer 觀察者實例(一個 window 窗口僅需一個 observer 實例)需要觀測多個元素的尺寸變化,所以回調函數(shù)的入?yún)⑹且粋€數(shù)組類型的數(shù)據(jù)結構突雪,每一個元素成 員都是一個 ResizeObserverEntry 實例(包含被觀測元素尺寸變化信息的一個對象)起惕。代碼中的最后一行使用此實例的 target(指向尺寸變化的目標元素)屬性進行事件派發(fā)功能。至此整個 DOM Resize 事件的自定義偵測功能我們就做好了咏删,下面就讓我們把它合并到瀏覽器原生的事件接口中去吧!
封裝事件到瀏覽器內(nèi)置接口
手動實現(xiàn) DOM Resize 事件的功能我們已經(jīng)做出來了惹想,但是我們在給 DOM 元素注冊事件的時候怎么才能知道需要去觀察它的尺寸呢?這就需要我們對注冊事件的程序進行攔截饵婆,在注冊事件之前加入自己的處理邏輯勺馆,然后再包裝成一個全新的事件偵聽器,下面讓我們直接來看代碼吧
const addEventListener = EventTarget.prototype.addEventTarget
HTMLElement.prototype.addEventTarget = function _wrapper(type) {
if (type === 'resize') {
registerEvent() //講到這里侨核,聰明的你一定能實現(xiàn)這個函數(shù)草穆,趕快動手去實現(xiàn)吧!
}
addEventTarget.apply(this, arguments)
}
首先我們對原有的DOM事件注冊偵聽器做一個緩存搓译,然后我們手動包裝一個事件偵聽器函數(shù)覆蓋到HTMLElement的原型對象上悲柱,這里簡單說明一下為什么是覆蓋到HTMLElement而不是Element,了解原型鏈的同學應該都知道,子類會繼承從父類到原型鏈末梢的所有屬性和方法些己,這里如果把事件偵聽器重寫到Element的原型上的話豌鸡,文檔中的所有節(jié)點類型都會繼承這個方法,就連文本節(jié)點上的原型方法也被覆蓋了段标,這就造成了不必要的功能浪費涯冠,所以這里只需讓HTML元素的節(jié)點注冊偵聽器生效即可。
重寫的事件偵聽器包裝函數(shù)內(nèi)部通過判斷當前注冊的事件類型實現(xiàn)自定義的事件注冊功能即可逼庞,這里以resize 事件攔截為例子蛇更,然后把前面講到的自定義事件功能實現(xiàn)的邏輯放到這里即可,代碼中以registerEvent這個函數(shù)為案例,你還可以實現(xiàn)其他的自定義事件派任。
文章的最后附上本項目的Github倉庫地址點擊查看
備注:本篇文章為作者原創(chuàng)砸逊,轉載請注明出處,謝謝掌逛!