動(dòng)畫???
動(dòng)畫就是間隔一段進(jìn)行進(jìn)行一個(gè)操作.
一般常用的動(dòng)畫處理模式有早期的定時(shí)器: setTimeout , setInterval
后續(xù) css3 帶來(lái)了一個(gè)新的動(dòng)畫過(guò)度屬性 transition.
對(duì)于 transition 能實(shí)現(xiàn)動(dòng)畫很好的過(guò)度效果,這沒(méi)有什么可說(shuō)的.
但是對(duì)于 setTimeout , setInterval 來(lái)言. 雖然定時(shí)了,但可能執(zhí)行的時(shí)機(jī)并不準(zhǔn)確.
為什么?
這就要提高 JavaScript 的 EventLoop 了.
在瀏覽器 JavaScript 的執(zhí)行環(huán)境是單線程的.
只有等待主隊(duì)列同步代碼執(zhí)行完畢了,才會(huì)去執(zhí)行 EventLoop 事件循環(huán)里的回調(diào)函數(shù)(無(wú)一例外).
假設(shè)現(xiàn)在有這么一樣場(chǎng)景.
使用 setInterval , setTimeout 來(lái)定時(shí) 200ms 執(zhí)行一段代碼.
此時(shí),同步代碼有 1000 萬(wàn)行,執(zhí)行完畢時(shí)間要 半分鐘.
那么此時(shí),setInterval 和 setTimeout 雖然你設(shè)定了 200ms 后就執(zhí)行,
但由于同步代碼數(shù)量龐大,執(zhí)行的優(yōu)先級(jí)又最高.
于是就導(dǎo)致了即使是同步代碼運(yùn)行效率很快,不到半分鐘,25秒就可以執(zhí)行完的話.
那么你的 EventLoop 至少也需要等待25s后才能執(zhí)行 setInterval 和 setTimeout.
這顯然和之前設(shè)置的 200ms 差距太遠(yuǎn)了.
這也就是為什么說(shuō),在某些情況下, setInterval 和 setTimeout 會(huì)出現(xiàn)執(zhí)行時(shí)機(jī)不準(zhǔn)的問(wèn)題.
requestAnimationFrame
顯示器是需要刷新的.
瀏覽器跑在顯示里. 也肯定是需要刷新的.
看一段 MDN 的解釋:
window.requestAnimationFrame() 告訴瀏覽器——你希望執(zhí)行一個(gè)動(dòng)畫奉呛,并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動(dòng)畫锐极。該方法需要傳入一個(gè)回調(diào)函數(shù)作為參數(shù),該回調(diào)函數(shù)會(huì)在瀏覽器下一次重繪之前執(zhí)行
注意:若你想在瀏覽器下次重繪之前繼續(xù)更新下一幀動(dòng)畫迄埃,那么回調(diào)函數(shù)自身必須再次調(diào)用window.requestAnimationFrame()
瀏覽器內(nèi)部元素如果有動(dòng)畫的存在,瀏覽器是需要刷新的.
每刷新一次就叫做一幀.
如果能有一個(gè)時(shí)機(jī),在每次刷新下一幀之前,就把動(dòng)畫元素的變化執(zhí)行完畢.而不是不管時(shí)機(jī)胡亂的執(zhí)行,那么動(dòng)畫效果肯定會(huì)更流暢.
于是 requestAnimationFrame
函數(shù)就應(yīng)運(yùn)而生了.
它就是用來(lái)更加流暢的處理動(dòng)畫的.(為什么是動(dòng)畫?如果瀏覽器僅僅展示一片靜態(tài)的文件,為了節(jié)省系統(tǒng)資源,它就可以不刷新了或者降低刷新頻率.)
div#box,
div#request-animation-box {
width: 100px;
height: 40px;
background-color: salmon;
}
<div id="box"></div>
<div id="request-animation-box"></div>
// 使用定時(shí)器的方式,由于存在eventLoop導(dǎo)致代碼優(yōu)先級(jí)的問(wèn)題.會(huì)出現(xiàn)定時(shí)并不準(zhǔn)確的情況.
let box = document.querySelector('#box'), rbox = document.querySelector('#request-animation-box')
normalWay()
function normalWay() {
let id = setInterval(() => {
box.style.width = `${++box.clientWidth}px`
if (box.clientWidth > 300) clearInterval(id)
console.log(box.style.width)
}, 17); // 1000ms / 17 = 每分鐘 60 fps
}
// rWay
rWay()
function rWay() {
rbox.style.width = `${++rbox.clientWidth}px` // 場(chǎng)景內(nèi),有物體的變化,必須刷新瀏覽器
if (rbox.clientWidth <= 300)
requestAnimationFrame(rWay) // 因?yàn)楸仨氁⑿聻g覽器,所以會(huì)調(diào)用 requestAnimationFrame .
// requestAimationFrame 的參數(shù)是一個(gè)函數(shù).
// 但是只調(diào)用一次,它只會(huì)在下一幀渲染之前調(diào)用.后續(xù)幀刷新時(shí),就不會(huì)調(diào)用.
// 所以需要不停的調(diào)用這個(gè)函數(shù)才可以.
}
一句話解釋就是:
瀏覽器說(shuō): 我馬上要刷新下一幀了,你有啥事情(動(dòng)畫)需要我在刷新下一幀之前做完的嗎? 有的話趕緊給我.
我說(shuō): 有啊!我需要你在每下一次幀數(shù)渲染之前執(zhí)行 rWay
方法.
瀏覽器說(shuō): 好的.我會(huì)讓我的小弟去干這個(gè)事情.對(duì)了,忘了告訴你了,我小弟的名字叫 requestAnimationFrame
沒(méi)有動(dòng)畫呢?
上面都說(shuō) requestAnimaitonFrame
, 動(dòng)畫,動(dòng)畫,和動(dòng)畫相關(guān).
那我的頁(yè)面里沒(méi)有動(dòng)畫邏輯,只有數(shù)據(jù)邏輯呢?
僅僅 1000 個(gè)數(shù)字的循環(huán),基本一瞬間就可以完成.
let counter = 1
function nothingInScreenChanged() {
++counter
console.log(counter)
if (counter < 1000) {
nothingInScreenChanged()
}
}
nothingInScreenChanged()
但是如果數(shù)字的循環(huán)和 reqeustAnimationFrame
添加上聯(lián)系呢? 這里并沒(méi)有我們可以在肉眼看到的變化,僅僅是程序內(nèi)部的數(shù)值發(fā)生了改變.
function nothingInScreenChanged() {
++counter
console.log(counter)
if (counter < 1000) { // 受制于屏幕刷新率最大 60fps , 數(shù)據(jù)自增的速度會(huì)明顯變慢
requestAnimationFrame(nothingInScreenChanged)
}
}
nothingInScreenChanged()
總結(jié):
- 瀏覽器的刷新機(jī)制是客觀存在的.
-
requestAnimationFrame
僅僅只是在瀏覽器每一次刷新渲染新幀之前的一個(gè)切片或者說(shuō)一個(gè)時(shí)機(jī).你用或者不用,這個(gè)切片都在那里. - 它和你是否執(zhí)行動(dòng)畫效果,還是單純的業(yè)務(wù)邏輯代碼并沒(méi)強(qiáng)求.我們可以將任意代碼都丟在這個(gè)切片里面執(zhí)行.
- 有時(shí)候,一些業(yè)務(wù)代碼要求的是執(zhí)行效率,丟在在這個(gè)切片里,受制于瀏覽器刷新性能,反而會(huì)拖慢整個(gè)流程的運(yùn)行.
reqeustAnimation
不要一提到,就是動(dòng)畫動(dòng)畫. 它僅僅是一個(gè)瀏覽器在每一幀渲染之間給予我們的一個(gè)切片時(shí)機(jī).僅此而已.