實(shí)現(xiàn)達(dá)到 60FPS 的高性能交互動(dòng)畫

原文鏈接?http://web.jobbole.com/92871/

瀏覽器 101:像素是怎么來(lái)的

在深入研究之前,我們要先搞清楚一個(gè)很重要的問(wèn)題:瀏覽器是怎么把代碼轉(zhuǎn)化成為用戶可見(jiàn)的像素點(diǎn)呢斩郎?

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

布局

渲染樹(shù)生成后怀泊,瀏覽器會(huì)從頁(yè)面左上角開(kāi)始迭代地計(jì)算出每個(gè)元素尺寸和位置,最終生成布局误趴。這個(gè)過(guò)程可能是一氣呵成的霹琼,但也可能由于元素的排列導(dǎo)致反復(fù)地繪制。元素間的位置關(guān)系都緊密相關(guān)凉当。為了優(yōu)化必要的任務(wù)枣申,瀏覽器會(huì)追蹤元素的變化情況,并將這些元素以及它們的子節(jié)點(diǎn)標(biāo)記為 ‘dirty’(臟元素)看杭。但是元素間耦合緊密忠藤,任何布局上的改變代價(jià)都是重大的,應(yīng)該盡量避免楼雹。

繪制

生成布局后模孩,瀏覽器將頁(yè)面繪制到屏幕上尖阔。這個(gè)環(huán)節(jié)和「布局」步驟類似,瀏覽器會(huì)追蹤臟元素榨咐,將它們合并到一個(gè)超大的矩形區(qū)域中介却。每一幀內(nèi)只會(huì)發(fā)生一次重繪,用于繪制這個(gè)被污染區(qū)域块茁。重繪也會(huì)消耗大量性能齿坷,能免則免

復(fù)合

最后一步龟劲,將所有繪制好的元素進(jìn)行復(fù)合胃夏。默認(rèn)情況下,所有元素將會(huì)被繪制到同一個(gè)層中昌跌;如果將元素分開(kāi)到不同的復(fù)合層中仰禀,更新元素對(duì)性能友好,不在同一層的元素不容易受到影響蚕愤。CPU 繪制層答恶,GPU 生成層∑加眨基礎(chǔ)繪圖操作在硬件加速合成中完成效率高悬嗓。層的分離允許非破壞性的改變,正如你所猜測(cè)的裕坊,GPU 復(fù)合層上的改變代價(jià)最小性能消耗最少包竹。

激發(fā)創(chuàng)造力

一般情況下,更改復(fù)合層是相對(duì)消耗性能較少的一個(gè)操作籍凝,所以盡量通過(guò)改變opacitytransform的值觸發(fā)復(fù)合層繪制周瞎。看起來(lái)好像…我們能做出的效果會(huì)很有限饵蒂,但真的是這樣嗎声诸?要好好開(kāi)發(fā)自己的創(chuàng)造力哦。

變換

「變換」為元素提供了無(wú)限的可能性:位置可以改變 (translateX,translateY, 或translate3d)退盯、大小也可以通過(guò)縮放 (scale) 改變彼乌、還能旋轉(zhuǎn)、斜切甚至 3D 變換渊迁。就是在某些場(chǎng)景下慰照,開(kāi)發(fā)者需要換一種思考方式,通過(guò)使用變換減少重排和重繪琉朽。 比如給一個(gè)元素添加 active 類名后它會(huì)向左移動(dòng) 10px毒租,可以通過(guò)改變 left 屬性:

透明度

可以通過(guò)改變opacity的值,實(shí)現(xiàn)元素的顯示和隱藏(與改變display或者visibility的值達(dá)到類似的效果類似漓骚,但性能更好)蝌衔。比如實(shí)現(xiàn)菜單的切換效果:菜單展開(kāi)時(shí),opacity值為1蝌蹂;收起時(shí)噩斟,opacity值變?yōu)?0。要注意的是pointer-events的值也要隨之改變孤个,防止用戶操作到明明收起的菜單剃允。closed 類名會(huì)根據(jù)用戶點(diǎn)擊 ‘open’ 時(shí),closed 類名會(huì)被加上齐鲤;點(diǎn)擊 ‘close’ 按鈕時(shí)斥废,closed 類名會(huì)被移除。對(duì)應(yīng)的代碼是這樣的:


另外给郊,透明度可變意味著開(kāi)發(fā)者可以控制元素的可見(jiàn)程度牡肉。多多思考應(yīng)用透明度的場(chǎng)景 — 比如直接給元素的陰影 (box-shadow) 做動(dòng)效很可能會(huì)造成嚴(yán)重的性能問(wèn)題:

如果把陰影放到偽元素上,控制偽元素的透明度從而控制陰影淆九,效果一樣但性能更好统锤,代碼如下:

手動(dòng)優(yōu)化

還有一個(gè)好消息 — 開(kāi)發(fā)者可以選擇想要控制的屬性,創(chuàng)建復(fù)合層炭庙,并將元素拖到該層饲窿。通過(guò)手動(dòng)優(yōu)化,確保元素總能被繪制好焕蹄,這也是通知瀏覽器準(zhǔn)備繪制該元素的最簡(jiǎn)單方式逾雄。需要獨(dú)立層的場(chǎng)景包括:元素的狀態(tài)將發(fā)生一些變化(比如動(dòng)畫)、改變了很消耗性能的樣式(比如position:fixed和overflow:scroll)腻脏⊙挥荆可能你也見(jiàn)過(guò)了糟糕的性能導(dǎo)致了頁(yè)面閃爍、震動(dòng)…或其他不如預(yù)期的效果迹卢,例如移動(dòng)端常見(jiàn)的固定在視口頂部的頭部辽故,會(huì)在頁(yè)面滾動(dòng)的時(shí)候閃爍。將這樣的元素獨(dú)立到自己的復(fù)合層腐碱,就是常見(jiàn)的解決這類問(wèn)題的方法誊垢。

hack 方法

從前,開(kāi)發(fā)者通常是通過(guò)backface-visibility:hidden或者trasform: translate3d(0,0,0)觸發(fā)瀏覽器生成新的復(fù)合層症见,但這并不是標(biāo)準(zhǔn)的寫法喂走,這兩種寫法也對(duì)元素的視覺(jué)效果不起作用。

新方法

現(xiàn)在有了will-change谋作,它能夠顯式地通知瀏覽器對(duì)某一個(gè)元素的某個(gè)或某些元素做渲染優(yōu)化芋肠。will-change接收各種各樣的屬性值,比如一個(gè)或多個(gè) CSS 屬性 (transform,opacity)遵蚜、contents或者scroll-position帖池。不過(guò)最常用值可能就是auto奈惑,這個(gè)值表示的是瀏覽器將進(jìn)行默認(rèn)的優(yōu)化:

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

動(dòng)畫方法

想要元素動(dòng)起來(lái)可以用 CSS(聲明式)庶柿,也可以使用 JavaScript(命令式),按需選擇秽浇。

聲明式動(dòng)畫

CSS 動(dòng)畫是聲明式的(告訴瀏覽器要做什么)浮庐,瀏覽器需要知道動(dòng)畫的起始狀態(tài)和終止?fàn)顟B(tài),這樣它才知道如何優(yōu)化兼呵。CSS 動(dòng)畫不是在主線程中執(zhí)行兔辅,不會(huì)妨礙主線程中的任務(wù)執(zhí)行』魑梗總的來(lái)說(shuō)维苔,CSS 動(dòng)畫對(duì)性能更友好。關(guān)鍵幀的動(dòng)畫組合提供了相當(dāng)豐富的視覺(jué)效果懂昂,比如下面是一個(gè)元素的無(wú)限旋轉(zhuǎn)動(dòng)畫:

但 CSS 動(dòng)畫缺乏 JS 的表達(dá)能力介时,將兩者結(jié)合起來(lái)效果更好:比如用 JS 監(jiān)聽(tīng)用戶輸入,根據(jù)動(dòng)作切換類名凌彬。類名對(duì)應(yīng)著不同的動(dòng)畫效果沸柔。下面的代碼實(shí)現(xiàn)的是當(dāng)元素被點(diǎn)擊時(shí)切換類名:

值得一提的是,如果你在操作「出血」(注:設(shè)計(jì)中在畫布四邊留出的一定區(qū)域稱為「出血」)時(shí)铲敛,新的Web Animation API會(huì)利用 CSS 的性能褐澎。通過(guò)這個(gè) API察藐,開(kāi)發(fā)者能輕松地在性能友好的基礎(chǔ)上處理動(dòng)畫的同步和時(shí)間問(wèn)題凉袱。

命令式動(dòng)畫

命令式動(dòng)畫告訴瀏覽器如何去演繹動(dòng)畫。CSS 動(dòng)畫代碼在某些場(chǎng)景下會(huì)變得很臃腫讲冠,或者需要更多的交互控制先鱼,此時(shí) JS 就要介入了俭正。注意!和 CSS 動(dòng)畫不同焙畔,JS 動(dòng)畫是在主線程中執(zhí)行的(也就是說(shuō)丟幀的可能性大于 CSS 動(dòng)畫的)掸读,性能相對(duì)差一些。在使用 JS 動(dòng)畫的場(chǎng)景中,考慮范圍中的性能之選比較少儿惫。

requestAnimationFrame

requestAnimationFrame對(duì)性能友好澡罚,你可以將它視作setTimeout的進(jìn)化版,不過(guò)這其實(shí)是一個(gè)動(dòng)畫執(zhí)行的 API肾请。理論上調(diào)用了這個(gè) API 就能保證 60fps 的幀率始苇,但實(shí)踐證明這個(gè)函數(shù)是請(qǐng)求在下一次可用時(shí)繪制動(dòng)畫,也就是并沒(méi)有固定的時(shí)間間隔筐喳。瀏覽器會(huì)把頁(yè)面上發(fā)生的變化組合接著一次繪制,而不會(huì)為每一次變化都進(jìn)行繪制函喉,通過(guò)這個(gè)方式提升 CPU 的使用率避归。 RAF 可以遞歸地使用

另外,類似縮放窗口或頁(yè)面滾動(dòng)這樣的場(chǎng)景管呵,直接綁定事件是相對(duì)消耗性能的梳毙,開(kāi)發(fā)者可以考慮在類似情況下用 RAF 提升性能。

滾動(dòng)

實(shí)現(xiàn)性能良好的平滑滾動(dòng)可是個(gè)挑戰(zhàn)捐下。幸運(yùn)的是账锹,最近規(guī)范提供一些可配置選項(xiàng)。開(kāi)發(fā)者不再需要通過(guò)禁止瀏覽器默認(rèn)行為 (preventDefault)坷襟,開(kāi)啟Passive event listeners即可提升滾動(dòng)性能(聲明之后奸柬,就不需要通過(guò)阻止元素的 touch 事件監(jiān)聽(tīng)和鼠標(biāo)滾輪事件監(jiān)聽(tīng)以優(yōu)化滾動(dòng)性能)。使用方法僅是在需要的監(jiān)聽(tīng)器中聲明{passive: true}:

從 Chrome 56 開(kāi)始婴程,這個(gè)選項(xiàng)將在touchmove和touchstart中默認(rèn)開(kāi)啟廓奕。

新出的Intersection Observer API能夠告訴開(kāi)發(fā)者某個(gè)元素是不是在視口內(nèi),或者是不是和其他元素有交互档叔。和通過(guò)事件處理這種會(huì)阻塞主線程的交互方式相比桌粉,Intersection Observer API 可以監(jiān)聽(tīng)元素,只有當(dāng)元素交叉路徑的時(shí)候才會(huì)執(zhí)行相應(yīng)操作衙四。這個(gè) API 在無(wú)限滾動(dòng)和懶加載的場(chǎng)景都可以使用铃肯。

先讀后寫

不斷地讀寫 DOM 會(huì)導(dǎo)致「強(qiáng)制同步布局」(forced synchronous layouts),不過(guò)在技術(shù)發(fā)展過(guò)程中它演變成了更形象的詞 — 「布局抖動(dòng)」(layout thrashing)传蹈。前文也有提到押逼,瀏覽器會(huì)追蹤「臟元素」,在合適的時(shí)候?qū)⒆儞Q過(guò)程儲(chǔ)存起來(lái)卡睦。在讀取了特定屬性以后宴胧,開(kāi)發(fā)者可以強(qiáng)制瀏覽器提前計(jì)算。這樣反復(fù)的讀寫會(huì)導(dǎo)致重排表锻。幸運(yùn)的是有一個(gè)簡(jiǎn)單的解決方式:讀完再寫恕齐。

為了模擬上述效果,請(qǐng)看下面這個(gè)對(duì)讀寫有嚴(yán)苛要求的例子:

將「讀」放到forEach外面,而不是和「寫」一起在每個(gè)迭代里都執(zhí)行显歧,就能提高性能:

優(yōu)化的未來(lái)

瀏覽器在性能優(yōu)化方面持續(xù)投入了越來(lái)越多的精力仪或。通過(guò)新屬性contain可以聲明一個(gè)元素的子樹(shù)獨(dú)立于頁(yè)面的其他元素(目前只有 Chrome 和 Opera 支持該屬性)。這就等于告訴了瀏覽器「這個(gè)元素是安全的士骤,它不會(huì)影響到其他元素」范删。contain的屬性值根據(jù)變化的范圍確定,可以是strict拷肌、content到旦、size、layout巨缘、style或者paint添忘。這確保了子樹(shù)被更新的時(shí)候,不會(huì)造成父元素的重排若锁。特別是在引入第三方控件的時(shí)候:

性能測(cè)試

知道了如何優(yōu)化頁(yè)面性能后搁骑,還要做性能測(cè)試才行。依我之見(jiàn)又固,Chrome 開(kāi)發(fā)者工具就是最棒的測(cè)試工具仲器。在 ‘More Tools’ 中有一個(gè) ‘Rendering’ 面板,其中包含了一些選項(xiàng):比如追蹤「臟元素」仰冠、計(jì)算每秒的幀率乏冀、高亮每層的邊界還有監(jiān)測(cè)滾動(dòng)性能問(wèn)題。

‘Performance’ 面板中的 ‘Timeline’ 工具能記錄動(dòng)畫過(guò)程洋只,開(kāi)發(fā)者可以直接定位到出問(wèn)題的部分煤辨。很簡(jiǎn)單,紅色表示有問(wèn)題木张,綠色表示渲染正常众辨。開(kāi)發(fā)者可以直接點(diǎn)擊紅色區(qū)域,看看是哪個(gè)函數(shù)造成了性能問(wèn)題的函數(shù)舷礼。

另一個(gè)有趣的工具是在 ‘Caputrue Settings’ 中的 ‘CPU throtting’鹃彻,開(kāi)發(fā)者可以通過(guò)這個(gè)選項(xiàng)模擬頁(yè)面運(yùn)行在一臺(tái)非常卡的設(shè)備上妻献。開(kāi)發(fā)者在桌面瀏覽器上測(cè)試頁(yè)面的時(shí)候效果可能很好蛛株,那是因?yàn)?PC 或者 Mac 的本身性能就優(yōu)于移動(dòng)設(shè)備。這個(gè)選項(xiàng)提供了很好的真機(jī)模擬育拨。

測(cè)試和迭代

動(dòng)畫性能優(yōu)化最簡(jiǎn)單的方案就是減少每一幀的工作量谨履。最有效緩解性能壓力的方法就是,盡量只更新在復(fù)合層中的元素熬丧,重新渲染復(fù)合層元素不容易影響到頁(yè)面上其他元素笋粟。性能優(yōu)化往往意味著反復(fù)地測(cè)試和驗(yàn)證,以及跳出慣性思維找到奇技淫巧實(shí)現(xiàn)高性能動(dòng)畫 — 無(wú)論怎么樣,最終受益的會(huì)是用戶和開(kāi)發(fā)者害捕。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绿淋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子尝盼,更是在濱河造成了極大的恐慌吞滞,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盾沫,死亡現(xiàn)場(chǎng)離奇詭異裁赠,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)赴精,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門组贺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人祖娘,你說(shuō)我怎么就攤上這事“⊙伲” “怎么了渐苏?”我有些...
    開(kāi)封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)菇夸。 經(jīng)常有香客問(wèn)我琼富,道長(zhǎng),這世上最難降的妖魔是什么庄新? 我笑而不...
    開(kāi)封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任鞠眉,我火速辦了婚禮,結(jié)果婚禮上择诈,老公的妹妹穿的比我還像新娘械蹋。我一直安慰自己,他們只是感情好羞芍,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布哗戈。 她就那樣靜靜地躺著,像睡著了一般荷科。 火紅的嫁衣襯著肌膚如雪唯咬。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天畏浆,我揣著相機(jī)與錄音胆胰,去河邊找鬼。 笑死刻获,一個(gè)胖子當(dāng)著我的面吹牛蜀涨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼勉盅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼佑颇!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起草娜,我...
    開(kāi)封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤挑胸,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后宰闰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體茬贵,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年移袍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了解藻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡葡盗,死狀恐怖螟左,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情觅够,我是刑警寧澤胶背,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站喘先,受9級(jí)特大地震影響钳吟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜窘拯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一红且、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涤姊,春花似錦暇番、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至搔涝,卻和暖如春厨喂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背庄呈。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工蜕煌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人诬留。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓斜纪,卻偏偏與公主長(zhǎng)得像贫母,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盒刚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容