成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

制作60fps的高性能動畫

617035918 / 1660人閱讀

摘要:因?yàn)楫惒降年P(guān)系中的回調(diào)函數(shù)并非立即執(zhí)行,而是需要加入等待隊(duì)列中。通知繪制位圖到屏幕上而就只需要繪制圖層了,所以硬件加速的性能無疑更好。

寫在前面

說到web的高性能動畫,這部分內(nèi)容其實(shí)已經(jīng)是老生常談的了,不過其中還是有不少比較新的而且非常實(shí)用的內(nèi)容可以和大家分享一下。
讀完這篇文章后相信大家都會對動畫渲染的機(jī)制以及制作60fps動畫的關(guān)鍵要素有足夠的理解,以后遇上了動畫相關(guān)的問題也可以很好的從源頭上解決。

正文 什么是高性能動畫呢?

動畫幀率可以作為衡量標(biāo)準(zhǔn),一般來說畫面在 60fps 的幀率下效果比較好。

換算一下就是,每一幀要在 16.7ms (16.7 = 60/1000) 內(nèi)完成渲染。因此,我們的首要任務(wù)是減少不必要的性能消耗。 越多的幀需要渲染的,意味著有越多的任務(wù)需要瀏覽器處理,所以掉幀就出現(xiàn)了,這是達(dá)到 60fps 的一個(gè)絆腳石。如果所有動畫都無法在 16.7ms 渲染完畢,不如考慮用略低的 30fps 幀率來渲染。

如何實(shí)現(xiàn)絲般順滑

這里主要決定因素有二:
時(shí)機(jī)(Frame Timing): 新的一幀準(zhǔn)備好的時(shí)機(jī)
成本(Frame Budget): 渲染新的一幀需要多長的時(shí)間

開始繪制的時(shí)機(jī)

一般來說我們使用setTimeout(callback, 1/60)來實(shí)現(xiàn)16.7ms后執(zhí)行動畫一幀的渲染。
然而setTimeout實(shí)際上并不準(zhǔn)確。
首先,setTimeout依靠瀏覽器內(nèi)置時(shí)鐘的更新頻率
例如:IE8及以前更新間隔為15.6ms,setTimeout(callback, 1/60)為16.7ms,那么它就需要兩個(gè)15.6ms才會觸發(fā),這也意味著無故延遲了 15.6 x 2 - 16.7 = 14.5毫秒。

其次,假使能夠達(dá)到16.7ms,它還要面臨一個(gè)異步隊(duì)列的問題。
因?yàn)楫惒降年P(guān)系setTimeout中的回調(diào)函數(shù)并非立即執(zhí)行,而是需要加入等待隊(duì)列中。但問題是,如果在等待延遲觸發(fā)的過程中,有新的同步腳本需要執(zhí)行,那么同步腳本不會排在timer的回調(diào)之后,而是立即執(zhí)行。

function runForSeconds(s) {
    var start = +new Date();
    while (start + s * 1000 > (+new Date())) {}
}

document.body.addEventListener("click", function () {
    runForSeconds(10);
}, false);

setTimeout(function () {
    console.log("Done!");
}, 1000 * 3);

以上的例子是,如果在等待觸發(fā)延遲的3秒過程中,有人點(diǎn)擊了body,那么回調(diào)還是準(zhǔn)時(shí)在3s完成時(shí)觸發(fā)嗎?
實(shí)踐執(zhí)行的時(shí)候,它會等待10s,同步函數(shù)總是優(yōu)先于異步函數(shù)。

基于這些問題我們提出了另一個(gè)解決方案:requestAnimationFrame(callback)

window.requestAnimationFrame() 方法告訴瀏覽器您希望執(zhí)行動畫并請求瀏覽器在下一次重繪之前調(diào)用指定的函數(shù)來更新動畫。該方法使用一個(gè)回調(diào)函數(shù)作為參數(shù),這個(gè)回調(diào)函數(shù)會在瀏覽器重繪之前調(diào)用。-- MDN

當(dāng)我們調(diào)用這個(gè)函數(shù)的時(shí)候,我們告訴它需要做兩件事:

我們需要新的一幀;

當(dāng)你渲染新的一幀時(shí)需要執(zhí)行我傳給你的回調(diào)函數(shù)

與 setTimeout 相比,rAF(requestAnimationFrame) 最大的優(yōu)勢是由系統(tǒng)來決定回調(diào)函數(shù)的執(zhí)行時(shí)機(jī)。具體一點(diǎn)講就是,系統(tǒng)每次繪制之前會主動調(diào)用 rAF 中的回調(diào)函數(shù),如果系統(tǒng)繪制率是 60Hz,那么回調(diào)函數(shù)就每16.7ms 被執(zhí)行一次,如果繪制頻率是75Hz,那么這個(gè)間隔時(shí)間就變成了 1000/75=13.3ms。換句話說就是,rAF 的執(zhí)行步伐跟著系統(tǒng)的繪制頻率走。它能保證回調(diào)函數(shù)在屏幕每一次的繪制間隔中只被執(zhí)行一次(函數(shù)節(jié)流,這篇文章就不細(xì)說了,感興趣的可以查一下),這樣就不會引起丟幀現(xiàn)象,也不會導(dǎo)致動畫出現(xiàn)卡頓的問題。

另外它可以自動調(diào)節(jié)頻率。如果callback工作太多無法在一幀內(nèi)完成會自動降低為30fps。雖然降低了,但總比掉幀好。

同時(shí)對比使用 setTimeout 實(shí)現(xiàn)的動畫,當(dāng)頁面被隱藏或最小化時(shí),setTimeout 仍然在后臺執(zhí)行動畫任務(wù),由于此時(shí)頁面處于不可見或不可用狀態(tài),刷新動畫是沒有意義的,而且還浪費(fèi) CPU 資源。而 rAF 則完全不同,當(dāng)頁面處理未激活的狀態(tài)下,該頁面的屏幕繪制任務(wù)也會被系統(tǒng)暫停,因此跟著系統(tǒng)步伐走的 rAF 也會停止渲染,當(dāng)頁面被激活時(shí),動畫就從上次停留的地方繼續(xù)執(zhí)行,有效節(jié)省了 CPU 開銷。

對于rAF的兼容性問題其實(shí)已經(jīng)有了很好的處理方案了,以下是一種比較簡單的:

window.requestAnimFrame = (function(){
 return  window.requestAnimationFrame   || 
   window.webkitRequestAnimationFrame || 
   window.mozRequestAnimationFrame    || 
   window.oRequestAnimationFrame      || 
   window.msRequestAnimationFrame     || 
   function( callback ){
        window.setTimeout(callback, 1000 / 60);
   };
})();

這種寫法沒有考慮 cancelAnimationFrame 的兼容性,并且不是所有的設(shè)備繪制時(shí)間間隔都是1000/60。
這個(gè)是比較不錯的polyfil 。

繪制一幀的時(shí)間

總的來說,rAF解決了前面的第一個(gè)問題(繪制時(shí)機(jī)),至于第二個(gè)問題(繪制成本),rAF是無能為力的,最多也就是采取自動降低頻率的方式處理。
這里就需要從瀏覽器渲染方面來優(yōu)化了,首先看下這個(gè)圖:

Rendering
頁面首次加載時(shí),瀏覽器會下載并解析 HTML,將 HTML 元素轉(zhuǎn)變?yōu)橐粋€(gè) DOM 節(jié)點(diǎn)的「內(nèi)容樹」(content tree)。除此之外,樣式同樣會被解析生成「渲染樹」 (render tree)。為了提升性能,渲染引擎會分開完成這些工作,甚至?xí)霈F(xiàn)渲染樹比 DOM 樹更快生成出來。

在這個(gè)階段里最影響繪制時(shí)間的自然就是Layout了

// animation loop
function update(timestamp) {
    for(var m = 0; m < movers.length; m++) {
        // DEMO 版本
        //movers[m].style.left = ((Math.sin(movers[m].offsetTop + timestamp/1000)+1) * 500) + "px";

        // FIXED 版本
        movers[m].style.left = ((Math.sin(m + timestamp/1000)+1) * 500) + "px";
        }
    rAF(update);
};
rAF(update);

上面例子里DEMO版本是非常慢的,之所以慢的原因是,在修改每一個(gè)物體的left值時(shí),會請求這個(gè)物體的offsetTop值,觸發(fā)了重排,這是一個(gè)非常耗時(shí)的reflow操作。

通常我們會不知不覺中寫了很多的頻繁layout的代碼,例如:

var h1 = element1.clientHeight;
element1.style.height = (h1 * 2) + "px";

var h2 = element2.clientHeight; 
element2.style.height = (h2 * 2) + "px";

var h3 = element3.clientHeight;
element3.style.height = (h3 * 2) + "px";

不斷地讀寫 DOM 會導(dǎo)致「強(qiáng)制同步布局」(forced synchronous layouts),不過在技術(shù)發(fā)展過程中它演變成了更形象的詞 — 「布局抖動」(layout thrashing)(詳情可以看一下這篇文章 layout thrashing)。
瀏覽器會追蹤「臟元素」,在合適的時(shí)候?qū)⒆儞Q過程儲存起來,然后在讀取了特定屬性以后,開發(fā)者可以強(qiáng)制瀏覽器提前計(jì)算,這樣反復(fù)的讀寫會導(dǎo)致重排。
所以這里我們需要進(jìn)行優(yōu)化,先讀后寫就是一個(gè)解決方案,上面的代碼可以改寫為:

// Read
var h1 = element1.clientHeight;
var h2 = element2.clientHeight;
var h3 = element3.clientHeight;

// Write
element1.style.height = (h1 * 2) + "px";
element2.style.height = (h2 * 2) + "px";
element3.style.height = (h3 * 2) + "px";

當(dāng)然這種只能應(yīng)對一些普通的情況,如果代碼是解耦的或者更復(fù)雜的讀寫后嵌套讀寫操作的這些情況可以使用一些比較成熟的解決方案,例如fastdom.js。另外一個(gè)小技巧是使用rAF來延遲全部的寫操作到下一幀執(zhí)行也是很不錯的解決方案。

Paint
生成布局后,瀏覽器將頁面繪制到屏幕上。這個(gè)環(huán)節(jié)和前一個(gè)步驟類似,瀏覽器會追蹤臟元素,將它們合并到一個(gè)超大的矩形區(qū)域中。每一幀內(nèi)只會發(fā)生一次重繪,用于繪制這個(gè)被污染區(qū)域。

這個(gè)階段對性能的影響主要在于重繪。

減少不必的繪制

例如,gif圖即使不可見,也可能導(dǎo)致paint,不需要時(shí)應(yīng)將gif圖的display屬性設(shè)為none
在經(jīng)常paint的區(qū)域,要避免代價(jià)太高的style
代價(jià)比較高的樣式:

color,border-style,visibility,background,
text-decoration,background-image,
background-position,background-repeat
outline-color,outline,outline-style
border-radius,outline-width,box-shadow
background-size

參考網(wǎng)站:https://csstriggers.com/

減少繪制的區(qū)域

為引起大范圍Paint的元素生成獨(dú)立的Layer以減小Paint的范圍
可以參考一下這個(gè)demo網(wǎng)站,綠色部分為重繪區(qū)域:

Composite

將所有繪制好的元素進(jìn)行復(fù)合。
默認(rèn)情況下,所有元素將會被繪制到同一個(gè)層中,如果將元素分開到不同的復(fù)合層中,更新元素對性能友好,不在同一層的元素不容易受到影響。
這一階段里CPU 繪制層,GPU 生成層。GPU 復(fù)合層上的改變代價(jià)最小性能消耗最少。所以這里的優(yōu)化主要就是把代價(jià)高的改動都放到GPU上,也就是一般說的開啟硬件加速技術(shù),可以說有益無害,如果設(shè)備的性能足夠開啟就對了。
這里的限制主要有:GPC和CPU之間帶寬,GPU的限度。

這里需要區(qū)分一下CPU,GPU的工作:

CPU工作比較多,還分主線程和合成線程。
主線程主要負(fù)責(zé):

Javascript 的計(jì)算與執(zhí)行

CSS 樣式計(jì)算

Layout 計(jì)算

將頁面元素繪制成位圖(paint),也就是光柵化(Raster)

將位圖給合成線程

合成線程則主要負(fù)責(zé):

將位圖(GraphicsLayer 層)以紋理(texture) 的形式上傳給 GPU(GPC和CPU之間帶寬)

計(jì)算頁面的可見部分和即將可見部分(滾動)

CSS 動畫處理(CSS 動畫而言,由于其流程不受主線程的影響,所以性能更好。)

通知 GPU 繪制位圖到屏幕上

而GPU就只需要繪制圖層了,所以硬件加速的性能無疑更好。

開啟硬件加速的方式主要有:

通過改變 opacitytransform 的值觸發(fā)

通過transform的3D屬性強(qiáng)制開啟GPU加速

will-change顯式地通知瀏覽器對某一個(gè)元素的某個(gè)或某些元素做渲染優(yōu)化

硬件加速之后,瀏覽器會為此元素多帶帶創(chuàng)建一個(gè)“層”。當(dāng)有多帶帶的層之后,此元素的repaint操作將只需要更新自己,不用影響到別人。你可以將其理解為局部更新。所以開啟了硬件加速的動畫會變得流暢很多

默認(rèn)情況下,transform、opacity這類css屬性CPU是直接通知GPU來做處理的,因?yàn)镚PU能快速對texture(紋理:CPU傳輸?shù)紾PU的一個(gè)Bitmap)進(jìn)行偏移、縮放、旋轉(zhuǎn)、修改透明度等操作,不經(jīng)過主線程的layout、paint過程。也就是開啟了硬件加速。

will-change是個(gè)新事物,它能夠顯式地通知瀏覽器對某一個(gè)元素的某個(gè)或某些元素做渲染優(yōu)化。 will-change 接收各種各樣的屬性值,比如一個(gè)或多個(gè) CSS 屬性 (transform, opacity)、contents 或者 scroll-position。不過最常用值可能就是 auto,這個(gè)值表示的是瀏覽器將進(jìn)行默認(rèn)的優(yōu)化:

GPU雖然擅長處理圖像,但是它也有瓶頸。連接CPU和GPU之間的帶寬是有限的,如果一次更新的層太多,則很容易就達(dá)到GPU的瓶頸,影響到動畫的流暢度。所以我們需要控制層的數(shù)量和層paint的次數(shù)。

控制層的數(shù)量可以理解,因?yàn)閷拥膭?chuàng)建和更新都會消耗內(nèi)存。而控制層paint的次數(shù),是為了減少位圖更新的次數(shù)。每次位圖更新,合成線程就需要提交新的位圖給GPU。頻繁地更新位圖也會拖慢GPU的效率。

優(yōu)化有度,我們總能聽到關(guān)于「復(fù)合層過多反而阻礙渲染」的討論。因?yàn)闉g覽器已經(jīng)為優(yōu)化做了能做的一切, will-change 的性能優(yōu)化方案本身對資源要求很高。如果瀏覽器持續(xù)在執(zhí)行某個(gè)元素的 will-change,就意味著瀏覽器要持續(xù)對這個(gè)元素的進(jìn)行優(yōu)化,性能消耗造成頁面卡頓。過多的復(fù)合層降低頁面性能的現(xiàn)象在移動端很常見。

避免意外生成的layer
z-index高于Layer的元素,也會生成多帶帶的Layer
demo以及說明頁面

小結(jié)

實(shí)現(xiàn)絲般順滑主要決定因素有二:
時(shí)機(jī)(Frame Timing):

rAF

成本(Frame Budget):

避免layout:先讀后寫

盡量少paint:注意樣式的使用

適當(dāng)?shù)挠布铀?/p>

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/97322.html

相關(guān)文章

  • 移動端滾動研究

    摘要:還會有一個(gè)性能上的問題就是當(dāng)頁面的列表過長,元素過多時(shí),在模擬滾動,下拉刷新這段時(shí)間內(nèi),頁面也會有卡頓現(xiàn)象,這里采取了一個(gè)優(yōu)化策略即列表較長時(shí)數(shù)量較多時(shí),在觸發(fā)下拉刷新的時(shí)機(jī)時(shí)將頁面視窗之外的元素隱藏或者存放在里面。 移動web滾動問題 在移動端如果使用局部滾動,意思就是我們的滾動在一個(gè)固定寬高的div內(nèi)觸發(fā),將該div設(shè)置成overflow:scroll/auto;來形成div內(nèi)部的...

    ghnor 評論0 收藏0
  • 前端動畫技術(shù)研究和比較

    摘要:它和前端動畫之間沒有包含與被包含的關(guān)系,更不能將它們混為一談,只有兩者的有機(jī)結(jié)合才能創(chuàng)建出炫酷的界面。 第一次在segmentfault上發(fā)文章 :),歡迎評論指正。原文最初發(fā)表在 https://github.com/WarpPrism/... 動畫相關(guān)概念 幀:動畫過程中每一個(gè)靜止的狀態(tài),每一張靜止的圖片 幀率:刷新頻率,每秒鐘播放的幀數(shù),F(xiàn)PS(frame per second...

    wushuiyong 評論0 收藏0
  • 前端動畫技術(shù)研究和比較

    摘要:它和前端動畫之間沒有包含與被包含的關(guān)系,更不能將它們混為一談,只有兩者的有機(jī)結(jié)合才能創(chuàng)建出炫酷的界面。 第一次在segmentfault上發(fā)文章 :),歡迎評論指正。原文最初發(fā)表在 https://github.com/WarpPrism/... 動畫相關(guān)概念 幀:動畫過程中每一個(gè)靜止的狀態(tài),每一張靜止的圖片 幀率:刷新頻率,每秒鐘播放的幀數(shù),F(xiàn)PS(frame per second...

    endiat 評論0 收藏0
  • 網(wǎng)頁性能分析不完全指南

    摘要:因此,如果可能,最好利用好毫秒響應(yīng)預(yù)先計(jì)算開銷大的工作,這樣網(wǎng)站就更有可能實(shí)現(xiàn)的性能??臻e主線程工作分成不大于毫秒的塊。點(diǎn)擊按鈕見圖示,會在頁面運(yùn)行時(shí)捕獲性能指標(biāo)。 前言 經(jīng)常能在博客或者論壇上看到很多有關(guān)前端性能優(yōu)化的文章,但是卻很少看到如何分析一個(gè)網(wǎng)頁的性能的文章。到底什么樣的指標(biāo)(或者說是標(biāo)準(zhǔn))代表這個(gè)網(wǎng)頁性能好或者不好,通過什么方式來得到這些指標(biāo)呢?因此,本文來講述下如何分析一...

    zgbgx 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<