新聞中心
本文在github做了收錄 github.com/Michael-lzg/my--article/blob/master/other/分享幾個實用的API.md:

綏中網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)公司!從網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、響應(yīng)式網(wǎng)站開發(fā)等網(wǎng)站項目制作,到程序開發(fā),運營維護。創(chuàng)新互聯(lián)公司從2013年開始到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗和運維經(jīng)驗,來保證我們的工作的順利進行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)公司。
- MutationObserver
- IntersectionObserver
- getComputedStyle()
- getBoundingClientRect
- requestAnimationFrame
MutationObserver
MutationObserver 是一個可以監(jiān)聽 DOM 結(jié)構(gòu)變化的接口。當 DOM 對象樹發(fā)生任何變動時,MutationObserver 會得到通知。
API
MutationObserver 是一個構(gòu)造器,接受一個 callback 參數(shù),用來處理節(jié)點變化的回調(diào)函數(shù),返回兩個參數(shù):
- mutations:節(jié)點變化記錄列表(sequence
) - observer:構(gòu)造 MutationObserver 對象。
MutationObserver 對象有三個方法,分別如下:
- observe:設(shè)置觀察目標,接受兩個參數(shù),target:觀察目標,options:通過對象成員來設(shè)置觀察選項。
- disconnect:阻止觀察者觀察任何改變。
- takeRecords:清空記錄隊列并返回里面的內(nèi)容。
//選擇一個需要觀察的節(jié)點
var targetNode = document.getElementById('root')
// 設(shè)置observer的配置選項
var config = { attributes: true, childList: true, subtree: true }
// 當節(jié)點發(fā)生變化時的需要執(zhí)行的函數(shù)
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.')
}
}
}
// 創(chuàng)建一個observer示例與回調(diào)函數(shù)相關(guān)聯(lián)
var observer = new MutationObserver(callback)
//使用配置文件對目標節(jié)點進行觀測
observer.observe(targetNode, config)
// 停止觀測
observer.disconnect()
observe 方法中 options 參數(shù)有已下幾個選項:
- childList:設(shè)置 true,表示觀察目標子節(jié)點的變化,比如添加或者刪除目標子節(jié)點,不包括修改子節(jié)點以及子節(jié)點后代的變化
- attributes:設(shè)置 true,表示觀察目標屬性的改變
- characterData:設(shè)置 true,表示觀察目標數(shù)據(jù)的改變
- subtree:設(shè)置為 true,目標以及目標的后代改變都會觀察
- attributeOldValue:如果屬性為 true 或者省略,則相當于設(shè)置為 true,表示需要記錄改變前的目標屬性值,設(shè)置了 attributeOldValue 可以省略 attributes 設(shè)置
- characterDataOldValue:如果 characterData 為 true 或省略,則相當于設(shè)置為 true,表示需要記錄改變之前的目標數(shù)據(jù),設(shè)置了 characterDataOldValue 可以省略 characterData 設(shè)置
- attributeFilter:如果不是所有的屬性改變都需要被觀察,并且 attributes 設(shè)置為 true 或者被忽略,那么設(shè)置一個需要觀察的屬性本地名稱(不需要命名空間)的列表
特點
MutationObserver 有以下特點:
- 它等待所有腳本任務(wù)完成后才會運行,即采用異步方式
- 它把 DOM 變動記錄封裝成一個數(shù)組進行處理,而不是一條條地個別處理 DOM 變動。
- 它即可以觀察發(fā)生在 DOM 節(jié)點的所有變動,也可以觀察某一類變動
當 DOM 發(fā)生變動會觸發(fā) MutationObserver 事件。但是,它與事件有一個本質(zhì)不同:事件是同步觸發(fā),也就是說 DOM 發(fā)生變動立刻會觸發(fā)相應(yīng)的事件;MutationObserver 則是異步觸發(fā),DOM 發(fā)生變動以后,并不會馬上觸發(fā),而是要等到當前所有 DOM 操作都結(jié)束后才觸發(fā)。
舉例來說,如果在文檔中連續(xù)插入 1000 個段落(p 元素),會連續(xù)觸發(fā) 1000 個插入事件,執(zhí)行每個事件的回調(diào)函數(shù),這很可能造成瀏覽器的卡頓;而 MutationObserver 完全不同,只在 1000 個段落都插入結(jié)束后才會觸發(fā),而且只觸發(fā)一次,這樣較少了 DOM 的頻繁變動,大大有利于性能。
IntersectionObserver
網(wǎng)頁開發(fā)時,常常需要了解某個元素是否進入了"視口"(viewport),即用戶能不能看到它。
傳統(tǒng)的實現(xiàn)方法是,監(jiān)聽到 scroll 事件后,調(diào)用目標元素的 getBoundingClientRect()方法,得到它對應(yīng)于視口左上角的坐標,再判斷是否在視口之內(nèi)。這種方法的缺點是,由于 scroll 事件密集發(fā)生,計算量很大,容易造成性能問題。
目前有一個新的 IntersectionObserver API,可以自動"觀察"元素是否可見,Chrome 51+ 已經(jīng)支持。由于可見(visible)的本質(zhì)是,目標元素與視口產(chǎn)生一個交叉區(qū),所以這個 API 叫做"交叉觀察器"。
API
IntersectionObserver 是瀏覽器原生提供的構(gòu)造函數(shù),接受兩個參數(shù):callback 是可見性變化時的回調(diào)函數(shù),option 是配置對象(該參數(shù)可選)。
var io = new IntersectionObserver(callback, option)
// 開始觀察
io.observe(document.getElementById('example'))
// 停止觀察
io.unobserve(element)
// 關(guān)閉觀察器
io.disconnect()
如果要觀察多個節(jié)點,就要多次調(diào)用這個方法。
io.observe(elementA)
io.observe(elementB)
目標元素的可見性變化時,就會調(diào)用觀察器的回調(diào)函數(shù) callback。callback 一般會觸發(fā)兩次。一次是目標元素剛剛進入視口(開始可見),另一次是完全離開視口(開始不可見)。
var io = new IntersectionObserver((entries) => {
console.log(entries)
})
callback 函數(shù)的參數(shù)(entries)是一個數(shù)組,每個成員都是一個 IntersectionObserverEntry 對象。舉例來說,如果同時有兩個被觀察的對象的可見性發(fā)生變化,entries 數(shù)組就會有兩個成員。
- time:可見性發(fā)生變化的時間,是一個高精度時間戳,單位為毫秒
- target:被觀察的目標元素,是一個 DOM 節(jié)點對象
- isIntersecting: 目標是否可見
- rootBounds:根元素的矩形區(qū)域的信息,getBoundingClientRect()方法的返回值,如果沒有根元素(即直接相對于視口滾動),則返回 null
- boundingClientRect:目標元素的矩形區(qū)域的信息
- intersectionRect:目標元素與視口(或根元素)的交叉區(qū)域的信息
- intersectionRatio:目標元素的可見比例,即 intersectionRect 占 boundingClientRect 的比例,完全可見時為 1,完全不可見時小于等于 0
舉個例子:
Document
首頁
相比于 getBoundingClientRect,它的優(yōu)點是不會引起重繪回流。兼容性如下
圖片懶加載
圖片懶加載的原理主要是判斷當前圖片是否到了可視區(qū)域這一核心邏輯實現(xiàn)的。這樣可以節(jié)省帶寬,提高網(wǎng)頁性能。
傳統(tǒng)的突破懶加載是通過監(jiān)聽 scroll 事件實現(xiàn)的,但是 scroll 事件會在很短的時間內(nèi)觸發(fā)很多次,嚴重影響頁面性能。為提高頁面性能,我們可以使用 IntersectionObserver 來實現(xiàn)圖片懶加載。
const imgs = document.querySelectorAll('img[data-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('data-src')
}
// 解除觀察
self.unobserve(entry.target)
}
})
}, config)
imgs.forEach((image) => {
observer.observe(image)
})
無限滾動
無限滾動(infinite scroll)的實現(xiàn)也很簡單。
var intersectionObserver = new IntersectionObserver(function (entries) {
// 如果不可見,就返回
if (entries[0].intersectionRatio <= 0) return
loadItems(10)
console.log('Loaded new items')
})
// 開始觀察
intersectionObserver.observe(document.querySelector('.scrollerFooter'))
getComputedStyle()
DOM2 Style 在 document.defaultView 上增加了 getComputedStyle()方法,該方法返回一個 CSSStyleDeclaration 對象(與 style 屬性的類型一樣),包含元素的計算樣式。
API
document.defaultView.getComputedStyle(element[,pseudo-element])
// or
window.getComputedStyle(element[,pseudo-element])
這個方法接收兩個參數(shù):要取得計算樣式的元素和偽元素字符串(如":after")。如果不需要查詢偽元素,則第二個參數(shù)可以傳 null。
和 style 的異同
getComputedStyle 和 element.style 的相同點就是二者返回的都是 CSSStyleDeclaration 對象。而不同點就是:
- element.style 讀取的只是元素的內(nèi)聯(lián)樣式,即寫在元素的 style 屬性上的樣式;而 getComputedStyle 讀取的樣式是最終樣式,包括了內(nèi)聯(lián)樣式、嵌入樣式和外部樣式。
- element.style 既支持讀也支持寫,我們通過 element.style 即可改寫元素的樣式。而 getComputedStyle 僅支持讀并不支持寫入。我們可以通過使用 getComputedStyle 讀取樣式,通過 element.style 修改樣式
getBoundingClientRect
getBoundingClientRect() 方法返回元素的大小及其相對于視口的位置。
API
let DOMRect = object.getBoundingClientRect()
它的返回值是一個 DOMRect 對象,這個對象是由該元素的 getClientRects() 方法返回的一組矩形的集合,就是該元素的 CSS 邊框大小。
返回的結(jié)果是包含完整元素的最小矩形,并且擁有 left, top, right, bottom, x, y, width, 和 height 這幾個以像素為單位的只讀屬性用于描述整個邊框。除了 width 和 height 以外的屬性是相對于視圖窗口的左上角來計算的。
應(yīng)用場景
1、獲取 dom 元素相對于網(wǎng)頁左上角定位的距離
以前的寫法是通過 offsetParent 找到元素到定位父級元素,直至遞歸到頂級元素 body 或 html。
// 獲取dom元素相對于網(wǎng)頁左上角定位的距離
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}
現(xiàn)在根據(jù) getBoundingClientRect 這個 api,可以寫成這樣:
var positionX = this.getBoundingClientRect().left + document.documentElement.scrollLeft
var positionY = this.getBoundingClientRect().top + document.documentElement.scrollTop
2、判斷元素是否在可視區(qū)域內(nèi)
function isElView(el) {
var top = el.getBoundingClientRect().top // 元素頂端到可見區(qū)域頂端的距離
var bottom = el.getBoundingClientRect().bottom // 元素底部端到可見區(qū)域頂端的距離
var se = document.documentElement.clientHeight // 瀏覽器可見區(qū)域高度。
if (top < se && bottom > 0) {
return true
} else if (top >= se || bottom <= 0) {
// 不可見
}
return false
}
requestAnimationFrame
window.requestAnimationFrame() 告訴瀏覽器——你希望執(zhí)行一個動畫,并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動畫。
API
該方法需要傳入一個回調(diào)函數(shù)作為參數(shù),該回調(diào)函數(shù)會在瀏覽器下一次重繪之前執(zhí)行。
window.requestAnimationFrame(callback)
兼容性處理
window._requestAnimationFrame = (function () {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60)
}
)
})()
結(jié)束動畫
var globalID
function animate() {
// done(); 一直運行
globalID = requestAnimationFrame(animate) // Do something animate
}
globalID = requestAnimationFrame(animate) //開始
cancelAnimationFrame(globalID) //結(jié)束
與 setTimeout 相比,requestAnimationFrame 最大的優(yōu)勢是由系統(tǒng)來決定回調(diào)函數(shù)的執(zhí)行時機。具體一點講,如果屏幕刷新率是 60Hz,那么回調(diào)函數(shù)就每 16.7ms 被執(zhí)行一次,如果刷新率是 75Hz,那么這個時間間隔就變成了 1000/75=13.3ms,換句話說就是,requestAnimationFrame 的步伐跟著系統(tǒng)的刷新步伐走。
它能保證回調(diào)函數(shù)在屏幕每一次的刷新間隔中只被執(zhí)行一次,這樣就不會引起丟幀現(xiàn)象,也不會導(dǎo)致動畫出現(xiàn)卡頓的問題。這個 API 的調(diào)用很簡單,如下所示:
var progress = 0
//回調(diào)函數(shù)
function render() {
progress += 1 //修改圖像的位置
if (progress < 100) {
//在動畫沒有結(jié)束前,遞歸渲染
window.requestAnimationFrame(render)
}
}
//第一幀渲染
window.requestAnimationFrame(render)
優(yōu)點:
- CPU 節(jié)能:使用 setTimeout 實現(xiàn)的動畫,當頁面被隱藏或最小化時,setTimeout 仍然在后臺執(zhí)行動畫任務(wù),由于此時頁面處于不可見或不可用狀態(tài),刷新動畫是沒有意義的,完全是浪費 CPU 資源。
- 而 requestAnimationFrame 則完全不同,當頁面處理未激活的狀態(tài)下,該頁面的屏幕刷新任務(wù)也會被系統(tǒng)暫停,因此跟著系統(tǒng)步伐走的 requestAnimationFrame 也會停止渲染,當頁面被激活時,動畫就從上次停留的地方繼續(xù)執(zhí)行,有效節(jié)省了 CPU 開銷。
- 函數(shù)節(jié)流:在高頻率事件(resize,scroll 等)中,為了防止在一個刷新間隔內(nèi)發(fā)生多次函數(shù)執(zhí)行,使用 requestAnimationFrame 可保證每個刷新間隔內(nèi),函數(shù)只被執(zhí)行一次,這樣既能保證流暢性,也能更好的節(jié)省函數(shù)執(zhí)行的開銷。一個刷新間隔內(nèi)函數(shù)執(zhí)行多次時沒有意義的,因為顯示器每 16.7ms 刷新一次,多次繪制并不會在屏幕上體現(xiàn)出來。
應(yīng)用場景
1、監(jiān)聽 scroll 函數(shù)
頁面滾動事件(scroll)的監(jiān)聽函數(shù),就很適合用這個 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、大量數(shù)據(jù)渲染
比如對十萬條數(shù)據(jù)進行渲染,主要由以下幾種方法:
(1)使用定時器
//需要插入的容器
let ul = document.getElementById('container')
// 插入十萬條數(shù)據(jù)
let total = 100000
// 一次插入 20 條
let once = 20
//總頁數(shù)
let page = total / once
//每條記錄的索引
let index = 0
//循環(huán)加載數(shù)據(jù)
function loop(curTotal, curIndex) {
if (curTotal <= 0) {
return false
}
//每頁多少條
let pageCount = Math.min(curTotal, once)
setTimeout(() => {
for (let i = 0; i < pageCount; 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)使用 requestAnimationFrame
//需要插入的容器
let ul = document.getElementById('container')
// 插入十萬條數(shù)據(jù)
let total = 100000
// 一次插入 20 條
let once = 20
//總頁數(shù)
let page = total / once
//每條記錄的索引
let index = 0
//循環(huán)加載數(shù)據(jù)
function loop(curTotal, curIndex) {
if (curTotal <= 0) {
return false
}
//每頁多少條
let pageCount = Math.min(curTotal, once)
window.requestAnimationFrame(function () {
for (let i = 0; i < pageCount; i++) {
let li = document.createElement('li')
li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
ul.appendChild(li)
}
loop(curTotal - pageCount, curIndex + pageCount)
})
}
loop(total, index)
監(jiān)控卡頓方法
每秒中計算一次網(wǎng)頁的 FPS,獲得一列數(shù)據(jù),然后分析。通俗地解釋就是,通過 requestAnimationFrame API 來定時執(zhí)行一些 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)
}
我們可以定義一些邊界值,比如連續(xù)出現(xiàn) 3 個低于 20 的 FPS 即可認為網(wǎng)頁存在卡頓。
當前文章:這幾個高級前端常用的API,你用到了嗎?
轉(zhuǎn)載源于:http://fisionsoft.com.cn/article/dppcode.html


咨詢
建站咨詢
