HTML动画 request animation frame
在網(wǎng)頁(yè)中,實(shí)現(xiàn)動(dòng)畫(huà)無(wú)外乎兩種方式。
1. CSS3 方式,也就是利用瀏覽器對(duì)CSS3 的原生支持實(shí)現(xiàn)動(dòng)畫(huà);
2. 腳本方式,通過(guò)間隔一段時(shí)間用JavaScript 來(lái)修改頁(yè)面元素樣式來(lái)實(shí)現(xiàn)動(dòng)畫(huà)。
接下來(lái)我們就分別介紹這兩種方式的原理,讓大家先對(duì)這兩種方式有一個(gè)直觀認(rèn)識(shí),了解各自的優(yōu)缺點(diǎn)。
CSS3 的方式下,開(kāi)發(fā)者一般在css 中定義一些包含CSS3 transition 語(yǔ)法的規(guī)則。在某些特定情況下,讓這些規(guī)則發(fā)生作用,于是瀏覽器就會(huì)將這些規(guī)則應(yīng)用于指定的DOM元素上,產(chǎn)生動(dòng)畫(huà)的效果。這種方式毫無(wú)疑問(wèn)運(yùn)行效率要比腳本方式高,因?yàn)闉g覽器原生支持,省去了JavaScript 的解釋執(zhí)行負(fù)擔(dān),有的瀏覽器(比如Chrome 瀏覽器)甚至還可以充分利用GPU 加速的優(yōu)勢(shì),進(jìn)一步增強(qiáng)了動(dòng)畫(huà)渲染的性能。不過(guò)CSS3 的方式并非完美,也有不少缺點(diǎn)。
首先, CSS3 Transition 對(duì)一個(gè)動(dòng)畫(huà)規(guī)則的定義是基于時(shí)間和速度曲線( Speed Curve)的規(guī)則。換句話來(lái)說(shuō),就是CSS3 的動(dòng)畫(huà)過(guò)程要描述成“在什么時(shí)間范圍內(nèi),以什么樣的運(yùn)動(dòng)節(jié)奏完成動(dòng)畫(huà)” 。
<!DOCTYPE html> <html><head><style> .sample {background: red;position: absolute;left: 0px;width: 100px;height: 100px;transition-property: left;transition-duration: 0.5s;transition-timing-function: ease } .sample:hover {left: 420px; }</style></head><body><div class="sample" /></body> </html>
在上面的例子中, sample 類的元素定義了這樣的動(dòng)畫(huà)屬性:“ left 屬性會(huì)在0.2 秒內(nèi)以ease 速度曲線完成動(dòng)畫(huà)” 。transition 只定義了動(dòng)畫(huà)涉及的屬性、時(shí)間和速度曲線,并不定義需要修改的具體值。sample 類的left 屬性默認(rèn)值為0 ,當(dāng)鼠標(biāo)移到sample 類元素上時(shí), left 屬性就擁有新的值420px 。這時(shí)候transition 定義的規(guī)則發(fā)生作用,讓left 屬性以ease 速度曲線在0.2 秒
的時(shí)間完成從0 變成420px 的轉(zhuǎn)化過(guò)程,這個(gè)過(guò)程中,用戶看到的就是sample 類元素向右移動(dòng)420 個(gè)像素的動(dòng)畫(huà)過(guò)程。
? ? ? ? 因?yàn)镃SS3 定義動(dòng)畫(huà)的方式是基于時(shí)間和速度曲線,可能不利于動(dòng)畫(huà)的流暢,因?yàn)閯?dòng)畫(huà)是可能會(huì)被中途打斷的,在上面的例子中,鼠標(biāo)移到sample 類元素上的時(shí)候開(kāi)始動(dòng)畫(huà),但是在0.2 秒的動(dòng)畫(huà)時(shí)間內(nèi),用戶的鼠標(biāo)可能會(huì)移出這個(gè)sample 類元素,這時(shí)候CSS3 還會(huì)以ease 速度曲線的節(jié)奏讓sample 類元素回到原位。從用戶體驗(yàn)角度來(lái)說(shuō),中途sample 類元素回到原位的動(dòng)作,語(yǔ)義上是“取消操作”的含義,但卻依然以同樣的時(shí)間和ease 節(jié)奏來(lái)完成“取消操作”的動(dòng)畫(huà),這并不合理。
? ? ? ? ? 時(shí)間和速度曲線的不合理是CSS3 先天的屬性,更讓開(kāi)發(fā)者頭疼的就是開(kāi)發(fā)CSS3 規(guī)則的過(guò)程,尤其是對(duì)transition-duration 時(shí)間很短的動(dòng)畫(huà)調(diào)試,因?yàn)镃SS3 的transition 過(guò)程總是一閃而過(guò),捕捉不到中間狀態(tài),只能一遍一遍用肉眼去檢驗(yàn)動(dòng)畫(huà)效果,用CSS3做過(guò)復(fù)雜動(dòng)畫(huà)的開(kāi)發(fā)者肯定都深有體會(huì)。雖然CSS3 有這樣一些缺點(diǎn),但是因?yàn)槠錈o(wú)與倫比的性能,用來(lái)處理一些簡(jiǎn)單的動(dòng)畫(huà)還是不錯(cuò)的選擇。
? ? ? ?相對(duì)于CSS3 方式,腳本方式最大的好處就是更強(qiáng)的靈活度,開(kāi)發(fā)者可以任意控制動(dòng)畫(huà)的時(shí)間長(zhǎng)度,也可以控制每個(gè)時(shí)間點(diǎn)上元素渲染出來(lái)的樣式,可以更容易做出豐富的動(dòng)畫(huà)效果。腳本方式的缺點(diǎn)也很明顯,動(dòng)畫(huà)過(guò)程通過(guò)JavaScript 實(shí)現(xiàn),不是瀏覽器原生支持,消耗的計(jì)算資源更多。如果處理不當(dāng),動(dòng)畫(huà)可能會(huì)出現(xiàn)卡頓滯后現(xiàn)象,本來(lái)使用動(dòng)畫(huà)是為了創(chuàng)造更好的用戶體驗(yàn),如果出現(xiàn)卡頓,反而對(duì)用戶體驗(yàn)帶來(lái)不好的影響。最原始的腳本方式就是利用setlnterval 或者setTimeout 來(lái)實(shí)現(xiàn),每隔一段時(shí)間一個(gè)指定的函數(shù)被執(zhí)行來(lái)修改界面的內(nèi)容或者樣式,從而達(dá)到動(dòng)畫(huà)的效果。
<!DOCTYPE html> <html><head><style> #sample {position: absolute;background: red;width: 100px;height: 100px; }</style></head><body><div id="sample" /><script type="text/javascript"> var animatedElement = document.getElementById("sample"); var left = 0; var timer; var ANIMATION_INTERVAL = 16;timer = setInterval(function() {left += 10;animatedElement.style.left = left + "px";if ( left >= 400 ) {clearInterval(timer);} }, ANIMATION_INTERVAL);</script></body> </html>
在上面的例子中,有一個(gè)常量ANIMATION INTERVAL 定義為16 , setlnterval 以這個(gè)常量為間隔,每16 毫秒計(jì)算一次sample 元素的left 值,每次都根據(jù)時(shí)間推移按比例增加left 的值,直到left 大于400 。為什么要選擇16 毫秒呢?因?yàn)槊棵脘秩?0 幀(也叫60fps, 60 Frame Per Second)會(huì)給用戶帶來(lái)足夠流暢的視覺(jué)體驗(yàn),一秒鐘有1000 毫秒, 1000 /60 =16 ,也就是說(shuō),如果我們做到每16 毫秒去渲染一次畫(huà)面,就能夠達(dá)到比較流暢的動(dòng)畫(huà)效果。對(duì)于簡(jiǎn)單的動(dòng)畫(huà), setlnterval 方式勉強(qiáng)能夠及格,但是對(duì)于稍微復(fù)雜一些的動(dòng)畫(huà),腳本方式就頂不住了,比如渲染一幀要花去超過(guò)32 毫秒的時(shí)間,那么還用16 毫秒一個(gè)間隔的方式肯定不行。實(shí)際上,因?yàn)橐粠秩疽加镁W(wǎng)頁(yè)線程32 毫秒,會(huì)導(dǎo)致setlnterval根本無(wú)法以16 毫秒間隔調(diào)用渲染函數(shù),這就產(chǎn)生了明顯的動(dòng)畫(huà)滯后感,原本一秒鐘完成的動(dòng)畫(huà)現(xiàn)在要花兩秒鐘完成,所以這種原始的setlnterval 方式是肯定不適合復(fù)雜的動(dòng)畫(huà)的。
? ? ? ?出現(xiàn)上面問(wèn)題的本質(zhì)原因是setlnterval 和setTimeout 并不能保證在指定時(shí)間間隔或者延遲的情況下準(zhǔn)時(shí)調(diào)用指定函數(shù)。所以可以換一個(gè)思路,當(dāng)指定函數(shù)調(diào)用的時(shí)候,根據(jù)逝去的時(shí)間計(jì)算當(dāng)前這一幀應(yīng)該顯示成什么樣子,這樣即使因?yàn)闉g覽器渲染主線程忙碌導(dǎo)致一幀渲染時(shí)間超過(guò)16 毫秒,在后續(xù)幀誼染時(shí)至少內(nèi)容不會(huì)因此滯后,即使達(dá)不倒60fps 的效果,也能保證動(dòng)畫(huà)在指定時(shí)間內(nèi)完成。
<!DOCTYPE html> <html><head><style> #sample {position: absolute;background: red;width: 100px;height: 100px; }</style></head><body><div id="sample" /><script type="text/javascript">var lastTimeStamp = new Date().getTime(); function raf(fn) {var currTimeStamp = new Date().getTime();var delay = Math.max(0, 16 - (currTimeStamp - lastTimeStamp));var handle = setTimeout(function(){fn(currTimeStamp);}, delay);lastTimeStamp = currTimeStamp;return handle; }var left = 0; var animatedElement = document.getElementById("sample"); var startTimestamp = new Date().getTime(); function render(timestamp) {left += (timestamp - startTimestamp) / 16;animatedElement.style.left = left + 'px';if (left < 400) {raf(render);} }raf(render);</script></body> </html>
在上面定義的raf 中,接受的fn 函數(shù)參數(shù)是真正的渲染過(guò)程, raf 只是協(xié)調(diào)渲染的節(jié)奏。raf 盡量以每隔16 毫秒的速度去調(diào)用傳遞的fn?參數(shù),如果發(fā)現(xiàn)上一次被調(diào)用時(shí)間和這一次被調(diào)用時(shí)間相差不足16 毫秒,就會(huì)保持16 毫秒一次的渲染間隔繼續(xù),如果發(fā)現(xiàn)
兩次調(diào)用時(shí)間間隔已經(jīng)超出了16 毫秒,就會(huì)在下一次時(shí)鐘周期立刻調(diào)用fn 。上面的render 函數(shù)中根據(jù)當(dāng)前時(shí)間和開(kāi)始動(dòng)畫(huà)的時(shí)間差來(lái)計(jì)算sample 元素的left 屬性,這樣無(wú)論render 函數(shù)何時(shí)被調(diào)用,總能夠渲染出正確的結(jié)果。
最后,我們將render 作為參數(shù)傳遞給raf ,啟動(dòng)了動(dòng)畫(huà)過(guò)程
轉(zhuǎn)載于:https://www.cnblogs.com/majiang/p/9691950.html
總結(jié)
以上是生活随笔為你收集整理的HTML动画 request animation frame的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 无题6
- 下一篇: 主元素问题 Majority Eleme