是一個可以監聽 DOM 結構變化的接口。當 DOM 對象樹發生任何變動時, 會得到通知。
API
是一個構造器,接受一個 參數,用來處理節點變化的回調函數,返回兩個參數:
對象有三個方法,分別如下:
//選擇一個需要觀察的節點
var?targetNode?=?document.getElementById( root )
//?設置observer的配置選項
var?config?=?{?attributes:?true,?childList:?true,?subtree:?true?}
//?當節點發生變化時的需要執行的函數
var?callback?=?function?(mutationsList,?observer)?{
??for?(var?mutation?of?mutationsList)?{
????if?(mutation.type?==? childList )?{
??????console.log( A?child?node?has?been?added?or?removed. )
????}?else?if?(mutation.type?==? attributes )?{
??????console.log( The? ?+?mutation.attributeName?+? ?attribute?was?modified. )
????}
??}
}
//?創建一個observer示例與回調函數相關聯
var?observer?=?new?MutationObserver(callback)
//使用配置文件對目標節點進行觀測
observer.observe(targetNode,?config)
//?停止觀測
observer.disconnect()
方法中 參數有已下幾個選項:
特點
有以下特點:
當 DOM 發生變動會觸發 事件。但是,它與事件有一個本質不同:事件是同步觸發,也就是說 DOM 發生變動立刻會觸發相應的事件; 則是異步觸發,DOM 發生變動以后,并不會馬上觸發,而是要等到當前所有 DOM 操作都結束后才觸發。
舉例來說,如果在文檔中連續插入 1000 個段落(p 元素),會連續觸發 1000 個插入事件,執行每個事件的回調函數,這很可能造成瀏覽器的卡頓;而 完全不同,只在 1000 個段落都插入結束后才會觸發,而且只觸發一次,這樣較少了 DOM 的頻繁變動,大大有利于性能。
網頁開發時,常常需要了解某個元素是否進入了"視口"(),即用戶能不能看到它。
傳統的實現方法是,監聽到 事件后,調用目標元素的 t()方法,得到它對應于視口左上角的坐標,再判斷是否在視口之內。這種方法的缺點是,由于 事件密集發生,計算量很大,容易造成性能問題。
目前有一個新的 API,可以自動"觀察"元素是否可見, 51+ 已經支持。由于可見()的本質是,目標元素與視口產生一個交叉區,所以這個 API 叫做"交叉觀察器"。
API
是瀏覽器原生提供的構造函數,接受兩個參數: 是可見性變化時的回調函數, 是配置對象(該參數可選)。
var?io?=?new?IntersectionObserver(callback,?option)
//?開始觀察
io.observe(document.getElementById( example ))
//?停止觀察
io.unobserve(element)
//?關閉觀察器
io.disconnect()
如果要觀察多個節點,就要多次調用這個方法。
io.observe(elementA)
io.observe(elementB)
目標元素的可見性變化時,就會調用觀察器的回調函數 。 一般會觸發兩次。一次是目標元素剛剛進入視口(開始可見),另一次是完全離開視口(開始不可見)。
var?io?=?new?IntersectionObserver((entries)?=>?{
??console.log(entries)
})
函數的參數()是一個數組,每個成員都是一個 Entry 對象。舉例來說,如果同時有兩個被觀察的對象的可見性發生變化, 數組就會有兩個成員。
舉個例子
<html?lang="en">
??<head>
????<meta?charset="UTF-8"?/>
????<title>Documenttitle>
????<style>
??????#div1?{
????????position:?sticky;
????????top:?0;
????????height:?50px;
????????line-height:?50px;
????????text-align:?center;
????????background:?black;
????????color:?#ffffff;
????????font-size:?18px;
??????}
????style>
??head>
??<body>
????<div?id="div1">首頁div>
????<div?style="height:?1000px;">div>
????<div?id="div2"?style="height:?100px;?background:?red;">div>
????<script>
??????var?div2?=?document.getElementById( div2 )
??????let?observer?=?new?IntersectionObserver(
????????function?(entries)?{
??????????entries.forEach(function?(element,?index)?{
????????????console.log(element)
????????????if?(element.isIntersecting)?{
??????????????div1.innerText?=? 我出來了
????????????}?else?{
??????????????div1.innerText?=? 首頁
????????????}
??????????})
????????},
????????{
??????????root:?null,
??????????threshold:?[0,?1]
????????}
??????)
??????observer.observe(div2)
????script>
??body>
html>
相比于 t,它的優點是不會引起重繪回流。兼容性如下
.png圖片懶加載
圖片懶加載的原理主要是判斷當前圖片是否到了可視區域這一核心邏輯實現的。這樣可以節省帶寬,提高網頁性能。傳統的突破懶加載是通過監聽 事件實現的,但是 事件會在很短的時間內觸發很多次,嚴重影響頁面性能。為提高頁面性能,我們可以使用 來實現圖片懶加載。
const?imgs?=?document.querySelectorAll( img[src] )
const?config?=?{
??rootMargin:? 0px ,
??threshold:?0
}
let?observer?=?new?IntersectionObserver((entries,?self)?=>?{
??entries.forEach((entry)?=>?{
????if?(entry.isIntersecting)?{
??????let?img?=?entry.target
??????let?src?=?img.dataset.src
??????if?(src)?{
????????img.src?=?src
????????img.removeAttribute( src )
??????}
??????//?解除觀察
??????self.unobserve(entry.target)
????}
??})
},?config)
imgs.forEach((image)?=>?{
??observer.observe(image)
})
無限滾動
無限滾動( )的實現也很簡單。
var?intersectionObserver?=?new?IntersectionObserver(function?(entries)?{
??//?如果不可見,就返回
??if?(entries[0].intersectionRatio?<=?0)?return
??loadItems(10)
??console.log( Loaded?new?items )
})
//?開始觀察
intersectionObserver.observe(document.querySelector( .scrollerFooter ))
()
DOM2 Style 在 . 上增加了 ()方法,該方法返回一個 對象(與 style 屬性的類型一樣),包含元素的計算樣式。
API
document.defaultView.getComputedStyle(element[,pseudo-element])
//?or
window.getComputedStyle(element[,pseudo-element])
這個方法接收兩個參數:要取得計算樣式的元素和偽元素字符串(如":after")。如果不需要查詢偽元素,則第二個參數可以傳 null。
html>
<html>
??<head>
????<style?type="text/css">
??????#myDiv?{
????????background-color:?blue;
????????width:?100px;
????????height:?200px;
??????}
????style>
??head>
??<body>
????<div?id="myDiv"?style="background-color:?red;?border:?1px?solid?black">div>
??body>
??<script>
????function?getStyleByAttr(obj,?name)?{
??????return?window.getComputedStyle???window.getComputedStyle(obj,?null)[name]?:?obj.currentStyle[name]
????}
????let?node?=?document.getElementById( myDiv )
????console.log(getStyleByAttr(node,? backgroundColor ))
????console.log(getStyleByAttr(node,? width ))
????console.log(getStyleByAttr(node,? height ))
????console.log(getStyleByAttr(node,? border ))
??script>
html>
和 style 的異同
和 .style 的相同點就是二者返回的都是 對象。而不同點就是:
t
t() 方法返回元素的大小及其相對于視口的位置。
API
let?DOMRect?=?object.getBoundingClientRect()
它的返回值是一個 對象高刷新率顯示器 寫代碼,這個對象是由該元素的 () 方法返回的一組矩形的集合,就是該元素的 CSS 邊框大小。返回的結果是包含完整元素的最小矩形,并且擁有 left, top, right, , x, y, width, 和 這幾個以像素為單位的只讀屬性用于描述整個邊框。除了 width 和 以外的屬性是相對于視圖窗口的左上角來計算的。
t.png應用場景
1、獲取 dom 元素相對于網頁左上角定位的距離
以前的寫法是通過 找到元素到定位父級元素,直至遞歸到頂級元素 body 或 html。
//?獲取dom元素相對于網頁左上角定位的距離
function?offset(el)?{
??var?top?=?0
??var?left?=?0
??do?{
????top?+=?el.offsetTop
????left?+=?el.offsetLeft
??}?while?((el?=?el.offsetParent))?//?存在兼容性問題,需要兼容
??return?{
????top:?top,
????left:?left
??}
}
var?odiv?=?document.getElementsByClassName( markdown-body )
offset(a[0])?//?{top:?271,?left:?136}
現在根據 t 這個 api,可以寫成這樣:
var?positionX?=?this.getBoundingClientRect().left?+?document.documentElement.scrollLeft
var?positionY?=?this.getBoundingClientRect().top?+?document.documentElement.scrollTop
2、判斷元素是否在可視區域內
function?isElView(el)?{
??var?top?=?el.getBoundingClientRect().top?//?元素頂端到可見區域頂端的距離
??var?bottom?=?el.getBoundingClientRect().bottom?//?元素底部端到可見區域頂端的距離
??var?se?=?document.documentElement.clientHeight?//?瀏覽器可見區域高度。
??if?(top??0)?{
????return?true
??}?else?if?(top?>=?se?||?bottom?<=?0)?{
????//?不可見
??}
??return?false
}
e
.e() 告訴瀏覽器——你希望執行一個動畫,并且要求瀏覽器在下次重繪之前調用指定的回調函數更新動畫。
API
該方法需要傳入一個回調函數作為參數,該回調函數會在瀏覽器下一次重繪之前執行。
window.requestAnimationFrame(callback)
兼容性處理
window._requestAnimationFrame?=?(function?()?{
??return?(
????window.requestAnimationFrame?||
????window.webkitRequestAnimationFrame?||
????window.mozRequestAnimationFrame?||
????function?(callback)?{
??????window.setTimeout(callback,?1000?/?60)
????}
??)
})()
結束動畫
var?globalID
function?animate()?{
??//?done();?一直運行
??globalID?=?requestAnimationFrame(animate)?//?Do?something?animate
}
globalID?=?requestAnimationFrame(animate)?//開始
cancelAnimationFrame(globalID)?//結束
與 相比,e 最大的優勢是由系統來決定回調函數的執行時機。具體一點講,如果屏幕刷新率是 60Hz,那么回調函數就每 16.7ms 被執行一次,如果刷新率是 75Hz,那么這個時間間隔就變成了 1000/75=13.3ms,換句話說就是,e 的步伐跟著系統的刷新步伐走。它能保證回調函數在屏幕每一次的刷新間隔中只被執行一次,這樣就不會引起丟幀現象,也不會導致動畫出現卡頓的問題。這個 API 的調用很簡單,如下所示:
var?progress?=?0
//回調函數
function?render()?{
??progress?+=?1?//修改圖像的位置
??if?(progress?100)?{
????//在動畫沒有結束前,遞歸渲染
????window.requestAnimationFrame(render)
??}
}
//第一幀渲染
window.requestAnimationFrame(render)
優點:應用場景
1、監聽 函數
頁面滾動事件()的監聽函數,就很適合用這個 api高刷新率顯示器 寫代碼,推遲到下一次重新渲染。
$(window).on( scroll ,?function?()?{
??window.requestAnimationFrame(scrollHandler)
})
平滑滾動到頁面頂部
const?scrollToTop?=?()?=>?{
??const?c?=?document.documentElement.scrollTop?||?document.body.scrollTop
??if?(c?>?0)?{
????window.requestAnimationFrame(scrollToTop)
????window.scrollTo(0,?c?-?c?/?8)
??}
}
scrollToTop()
2、大量數據渲染
比如對十萬條數據進行渲染,主要由以下幾種方法:
(1)使用定時器
//需要插入的容器
let?ul?=?document.getElementById( container )
//?插入十萬條數據
let?total?=?100000
//?一次插入?20?條
let?once?=?20
//總頁數
let?page?=?total?/?once
//每條記錄的索引
let?index?=?0
//循環加載數據
function?loop(curTotal,?curIndex)?{
??if?(curTotal?<=?0)?{
????return?false
??}
??//每頁多少條
??let?pageCount?=?Math.min(curTotal,?once)
??setTimeout(()?=>?{
????for?(let?i?=?0;?i???????let?li?=?document.createElement( li )
??????li.innerText?=?curIndex?+?i?+? ?:? ?+?~~(Math.random()?*?total)
??????ul.appendChild(li)
????}
????loop(curTotal?-?pageCount,?curIndex?+?pageCount)
??},?0)
}
loop(total,?index)
(2)使用 e
//需要插入的容器
let?ul?=?document.getElementById( container )
//?插入十萬條數據
let?total?=?100000
//?一次插入?20?條
let?once?=?20
//總頁數
let?page?=?total?/?once
//每條記錄的索引
let?index?=?0
//循環加載數據
function?loop(curTotal,?curIndex)?{
??if?(curTotal?<=?0)?{
????return?false
??}
??//每頁多少條
??let?pageCount?=?Math.min(curTotal,?once)
??window.requestAnimationFrame(function?()?{
????for?(let?i?=?0;?i???????let?li?=?document.createElement( li )
??????li.innerText?=?curIndex?+?i?+? ?:? ?+?~~(Math.random()?*?total)
??????ul.appendChild(li)
????}
????loop(curTotal?-?pageCount,?curIndex?+?pageCount)
??})
}
loop(total,?index)
監控卡頓方法
每秒中計算一次網頁的 FPS,獲得一列數據,然后分析。通俗地解釋就是,通過 e API 來定時執行一些 JS 代碼,如果瀏覽器卡頓,無法很好地保證渲染的頻率,1s 中 frame 無法達到 60 幀,即可間接地反映瀏覽器的渲染幀率。
var?lastTime?=?performance.now()
var?frame?=?0
var?lastFameTime?=?performance.now()
var?loop?=?function?(time)?{
??var?now?=?performance.now()
??var?fs?=?now?-?lastFameTime
??lastFameTime?=?now
??var?fps?=?Math.round(1000?/?fs)
??frame++
??if?(now?>?1000?+?lastTime)?{
????var?fps?=?Math.round((frame?*?1000)?/?(now?-?lastTime))
????frame?=?0
????lastTime?=?now
??}
??window.requestAnimationFrame(loop)
}
我們可以定義一些邊界值,比如連續出現 3 個低于 20 的 FPS 即可認為網頁存在卡頓。
——本文完 ——