摘要:在樣式代碼添加錄制性能日志如下可見(jiàn),已經(jīng)不存在繪制的步驟了。下面通過(guò)一段代碼模擬頁(yè)面進(jìn)入的過(guò)程,來(lái)演示這個(gè)問(wèn)題運(yùn)行效果如下可以看到,固定定位的黃色元素是在動(dòng)畫結(jié)束后才突然出現(xiàn)的。
對(duì)于移動(dòng)端的Web單頁(yè)應(yīng)用來(lái)說(shuō),為了達(dá)到媲美原生應(yīng)用的效果,頁(yè)面過(guò)渡動(dòng)畫是必不可少的。常用的頁(yè)面過(guò)渡動(dòng)畫包括:
位移——當(dāng)前頁(yè)向左側(cè)或右側(cè)水平移出可視區(qū),下一頁(yè)由反方向移入可視區(qū)。
不透明度變化——當(dāng)前頁(yè)淡出,下一頁(yè)淡入。
1和2同時(shí)進(jìn)行。
(注意:以下討論和實(shí)驗(yàn)均在 Chrome 68 瀏覽器環(huán)境下進(jìn)行)
目前大多數(shù)設(shè)備的屏幕刷新率為60次/秒,算下來(lái)每個(gè)幀的預(yù)算時(shí)間約為16.66毫秒(1/60秒)??紤]到瀏覽器還有其他工作要執(zhí)行,實(shí)際上預(yù)算時(shí)間只有10毫秒。跟此預(yù)算時(shí)間的差值越大,用戶就會(huì)覺(jué)得動(dòng)畫過(guò)程越卡。那么,在這10毫秒內(nèi)要完成什么事情呢?當(dāng)使用JavaScript實(shí)現(xiàn)視覺(jué)交互效果時(shí),一般要經(jīng)過(guò)以下流程:
JavaScript的執(zhí)行。例如修改元素的樣式,或者給元素添加/刪除樣式類。
樣式計(jì)算。根據(jù)樣式規(guī)則計(jì)算出元素的最終樣式。
布局(layout)。根據(jù)上一步的結(jié)果,計(jì)算元素占據(jù)的空間大小及其在屏幕的位置。注意,一個(gè)元素布局上的變化有可能會(huì)引發(fā)其他元素的聯(lián)動(dòng)變化。
繪制(paint)。填充像素的過(guò)程,包括元素的每個(gè)可視部分。一般來(lái)說(shuō),繪制是在多個(gè)層上進(jìn)行的。
合成(composite)。把各層按正確順序合并成一個(gè)層,顯示到屏幕上。
值得注意的是,并非每一幀都會(huì)經(jīng)過(guò)上述每一個(gè)步驟的處理。如果元素的幾何屬性(尺寸、位置)沒(méi)有變化,就不需要進(jìn)行布局;如果連元素的外觀都沒(méi)有改變,就不需要繪制。所以,實(shí)現(xiàn)流暢動(dòng)畫的關(guān)鍵就在于如何減少布局和繪制。
位移對(duì)于位移動(dòng)畫來(lái)說(shuō),最直接的實(shí)現(xiàn)方式,就是把元素設(shè)成絕對(duì)定位,然后去改變它的left樣式值。例如:
使用Chrome開(kāi)發(fā)者工具中的Performance面板錄制動(dòng)畫過(guò)程的性能日志,如下圖所示:
可見(jiàn),元素在移動(dòng)的過(guò)程中不斷觸發(fā)了布局和繪制。所以,這種實(shí)現(xiàn)方式的性能是極低的。網(wǎng)上諸多文獻(xiàn)會(huì)推薦以transform的變化代替left的變化,而實(shí)際情況又是怎么樣呢?把樣式代碼稍作修改:
.page { position: absolute; left: 0; top: 0; width: 100%; min-height: 100%; background: #ffffd; transition-duration: 2s; transition-property: transform; } .leave { transform: translateX(-100%); }
錄制性能日志如下圖所示:
可見(jiàn),僅僅是在動(dòng)畫開(kāi)始和結(jié)束兩個(gè)時(shí)間點(diǎn)觸發(fā)了繪制,而布局則完全沒(méi)有觸發(fā)。這樣一來(lái),性能就有了很大的提升。但是,這里還有兩個(gè)疑問(wèn):
為什么transform動(dòng)畫過(guò)程沒(méi)有觸發(fā)布局和繪制?
為什么動(dòng)畫開(kāi)始前觸發(fā)了兩次繪制,動(dòng)畫結(jié)束之后觸發(fā)了一次繪制?
要回答這兩個(gè)問(wèn)題,就得了解合成層。
合成層當(dāng)滿足某些條件的時(shí)候,元素在渲染時(shí)會(huì)被分配到一個(gè)獨(dú)立的層中進(jìn)行渲染,只要該層的內(nèi)容不發(fā)生改變,就不會(huì)觸發(fā)繪制,瀏覽器會(huì)直接通過(guò)合成形成一個(gè)新的幀。常見(jiàn)的提升為合成層的條件包括:
對(duì)opacity或transform應(yīng)用了animation或transition;
有 3D transform ;
will-change設(shè)置為opacity或transform。
很明顯,上一節(jié)的transform位移動(dòng)畫滿足了第一個(gè)條件。所以整個(gè)動(dòng)畫的渲染過(guò)程是這樣的:
動(dòng)畫開(kāi)始時(shí),由于div.page被提升為獨(dú)立的合成層,所以它要重新繪制;而document所在層相當(dāng)于少了一塊內(nèi)容,也得重新繪制;
動(dòng)畫過(guò)程中,div.page沒(méi)有其他變化,所以不觸發(fā)布局和繪制;
動(dòng)畫結(jié)束后,div.page不再是獨(dú)立的合成層,回到了document所在層,所以document又重新繪制了一遍。
如果讓div.page一直在獨(dú)立的合成層中渲染,則可以省掉上述過(guò)程中繪制的環(huán)節(jié)。在樣式代碼添加「will-change: transform」:
.page { position: absolute; left: 0; top: 0; width: 100%; min-height: 100%; background: #ffffd; transition-duration: 2s; transition-property: transform; will-change: transform; }
錄制性能日志如下:
可見(jiàn),已經(jīng)不存在繪制的步驟了。
順帶一提,Chrome開(kāi)發(fā)者工具中有一個(gè)Layers面板,可以方便地查看頁(yè)面上合成層以及成為合成層的原因。
(注意:由于低版本瀏覽器不支持will-change,所以實(shí)際應(yīng)用中,如果想把元素提升到獨(dú)立的合成層中渲染,可以用「transform: translateZ(0)」)
不透明度眾所周知,不透明度就是通過(guò)opacity樣式來(lái)控制的。那么opacity的變化是否會(huì)觸發(fā)布局和繪制呢?把樣式代碼修改如下:
.page { position: absolute; left: 0; top: 0; width: 100%; min-height: 100%; background: #ffffd; transition-duration: 2s; transition-property: opacity; } .leave { opacity: 0; }
錄制性能日志如下圖所示:
在常規(guī)認(rèn)知中,opacity的變化并不會(huì)導(dǎo)致元素位置和尺寸的變化,理應(yīng)不會(huì)觸發(fā)布局。但上述過(guò)程中確實(shí)觸發(fā)了一次布局,表現(xiàn)較為詭異。接下來(lái)給div.page添加「will-change: opacity」使其一直在獨(dú)立的合成層中渲染。錄制性能日志如下:
可見(jiàn),還是會(huì)觸發(fā)一次繪制。而針對(duì)這「一次的布局」和「一次的繪制」,我進(jìn)行了進(jìn)一步的實(shí)驗(yàn),得出的結(jié)論是:opacity從1(包括未設(shè)置的情況,下同)變更到小于1,以及從小于1變更到1,都會(huì)觸發(fā)布局和繪制;即使在獨(dú)立的合成層中渲染,也只能省掉布局,無(wú)法省掉繪制。
由于在opacity動(dòng)畫過(guò)程中從1到小于1的變更只會(huì)有一次,所以上述的布局和繪制都只觸發(fā)一次。
位移和不透明度同時(shí)使用兩種動(dòng)畫,修改樣式代碼如下:
.page { position: absolute; left: 0; top: 0; width: 100%; min-height: 100%; background: #ffffd; transition-duration: 2s; transition-property: transform, opacity; } .leave { transform: translateX(-100%); opacity: 0; }
按照前文的描述,動(dòng)畫過(guò)程會(huì)觸發(fā):
一次布局,在動(dòng)畫開(kāi)始時(shí)觸發(fā),由opacity引起;
兩次繪制,在動(dòng)畫開(kāi)始時(shí)觸發(fā),因opacity以及提升為獨(dú)立合成層引起;
由獨(dú)立合成層回到document所在層時(shí)引起。
倘若加上「will-change: transform, opacity」,使div.page一直在獨(dú)立的合成層中渲染,則只觸發(fā)一次繪制,由opacity引起。
然而,創(chuàng)建一個(gè)新的合成層并不是免費(fèi)的,它會(huì)導(dǎo)致額外的內(nèi)存開(kāi)銷。在單頁(yè)應(yīng)用中,應(yīng)用頁(yè)面過(guò)渡動(dòng)畫的元素是頁(yè)面的最外層容器,包含了該頁(yè)面所有內(nèi)容結(jié)構(gòu)。如果讓其長(zhǎng)期在獨(dú)立的合成層中渲染,那內(nèi)存的消耗是非常大的。
所以,可以僅在動(dòng)畫過(guò)程中讓其在獨(dú)立的合成層中渲染,而在其他情況下則維持常規(guī)狀態(tài)。
transform和fixed的沖突如果用transform實(shí)現(xiàn)頁(yè)面過(guò)渡動(dòng)畫,想必大家都遇到過(guò)一個(gè)問(wèn)題:頁(yè)面上固定定位的元素,其位置變得不太正常了。
下面通過(guò)一段代碼模擬頁(yè)面進(jìn)入的過(guò)程,來(lái)演示這個(gè)問(wèn)題:
運(yùn)行效果如下:
可以看到,固定定位的黃色元素是在動(dòng)畫結(jié)束后才突然出現(xiàn)的。那在這之前它跑到哪去了呢?
如果給一個(gè)固定定位元素的任意一個(gè)祖先元素設(shè)置樣式「transform」或者「will-change: transform」,那么該元素就會(huì)相對(duì)于最近的設(shè)置了上述樣式的祖先元素定位。
因?yàn)閐iv.page的高度設(shè)成了150%,所以,在動(dòng)畫過(guò)程中,黃色元素實(shí)際上是跑到了頁(yè)面的最底下(超出了瀏覽器可視范圍)去了。而在某些比較舊(如 iOS 9 的Safari)的移動(dòng)端瀏覽器中,問(wèn)題更為嚴(yán)重,固定定位的元素可能會(huì)消失掉再也不出現(xiàn)。
網(wǎng)上能查到的解決方案有兩種:
通過(guò)絕對(duì)定位模擬固定定位。雖然是可行的,但是在移動(dòng)端瀏覽器內(nèi),交互上會(huì)有一些細(xì)節(jié)問(wèn)題,而且元素內(nèi)部的滾動(dòng)很容易與頁(yè)面滾動(dòng)沖突。
把固定定位的元素放到應(yīng)用transform動(dòng)畫的元素外。但這對(duì)使用「Vue.js」這類框架開(kāi)發(fā)的單頁(yè)應(yīng)用來(lái)說(shuō)可行性較低,因?yàn)樵谶@類框架中,一個(gè)頁(yè)面就是一個(gè)組件,多帶帶把頁(yè)面中的某個(gè)元素抽離出來(lái)是比較麻煩的。
所以,這里介紹第三種方案——在頁(yè)面過(guò)渡動(dòng)畫結(jié)束之后(此時(shí)transform樣式已被移除,不再影響fixed),再讓固定定位的元素插入到頁(yè)面容器。并且,為了讓它的出現(xiàn)顯得不那么突然,增加緩動(dòng)動(dòng)畫。代碼主要修改點(diǎn)如下:
@keyframes kf-move-in { 0% { transform: translateY(100%); } 100% { transform: translateY(0); } } .move-in { animation-name: kf-move-in; animation-duration: 0.45s; }
運(yùn)行效果如下:
這樣一來(lái),整個(gè)交互就較為友好了。這同時(shí)也說(shuō)明:技術(shù)上的問(wèn)題,不一定只能通過(guò)技術(shù)去解決,也可以從交互上去尋求解決方案。
參考文獻(xiàn)《渲染性能》
《堅(jiān)持僅合成器的屬性和管理層計(jì)數(shù)》
《無(wú)線性能優(yōu)化:Composite》
本文同時(shí)發(fā)布于作者個(gè)人博客: https://mrluo.life/article/de...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/114317.html
摘要:在樣式代碼添加錄制性能日志如下可見(jiàn),已經(jīng)不存在繪制的步驟了。下面通過(guò)一段代碼模擬頁(yè)面進(jìn)入的過(guò)程,來(lái)演示這個(gè)問(wèn)題運(yùn)行效果如下可以看到,固定定位的黃色元素是在動(dòng)畫結(jié)束后才突然出現(xiàn)的。 對(duì)于移動(dòng)端的Web單頁(yè)應(yīng)用來(lái)說(shuō),為了達(dá)到媲美原生應(yīng)用的效果,頁(yè)面過(guò)渡動(dòng)畫是必不可少的。常用的頁(yè)面過(guò)渡動(dòng)畫包括: 位移——當(dāng)前頁(yè)向左側(cè)或右側(cè)水平移出可視區(qū),下一頁(yè)由反方向移入可視區(qū)。 不透明度變化——當(dāng)前頁(yè)淡...
摘要:在的發(fā)展過(guò)程中,是最早與之父合作的人之一。問(wèn)您認(rèn)為中國(guó)的開(kāi)發(fā)者雖然起步晚,但是現(xiàn)在已經(jīng)趕上了是的。但是我知道,它們只是進(jìn)化的一部分。第一個(gè)最主要的原因就是要保護(hù)。 非商業(yè)轉(zhuǎn)載請(qǐng)注明作譯者、出處,并保留本文的原始鏈接:http://www.ituring.com.cn/article/194473 Bert Bos是一位計(jì)算機(jī)科學(xué)家,他也是CSS的創(chuàng)始人之一。在CSS的發(fā)展過(guò)程...
摘要:春招結(jié)果五月份了,春招已經(jīng)接近尾聲,因?yàn)榈搅酥芪逋砩蟿偤糜锌?,所以?jiǎn)單地記錄一下自己的春招過(guò)程。我的春招從二月初一直持續(xù)到四月底,截止今天,已經(jīng)斬獲唯品會(huì)電商前端研發(fā)部大數(shù)據(jù)與威脅分析事業(yè)部京東精銳暑假實(shí)習(xí)生的騰訊的是早上打過(guò)來(lái)的。 春招結(jié)果 五月份了,春招已經(jīng)接近尾聲,因?yàn)榈搅酥芪逋砩蟿偤糜锌?,所以?jiǎn)單地記錄一下自己的春招過(guò)程。我的春招從二月初一直持續(xù)到四月底,截止今天,已經(jīng)斬獲唯品...
摘要:下面具體說(shuō)一說(shuō)四次面試經(jīng)歷,已經(jīng)問(wèn)到的問(wèn)題,現(xiàn)在就做一次總結(jié)。第四次面試第四家公司真的就是高大上了,在騰訊的旁邊,先不說(shuō)面試,先說(shuō)騰訊,真的就是當(dāng)時(shí)內(nèi)心挺害怕的。有點(diǎn)不好意思的說(shuō)就是當(dāng)時(shí)站在騰訊大樓面前腿是有些瑟瑟發(fā)抖的。 前言 做一個(gè)自我介紹,本人男,愛(ài)好女。曾以為自己可以改變世界,沒(méi)想到被世界無(wú)情的摧殘。來(lái)深圳之前那種找工作少于 1W 少跟我談,變成了收到 offer 了 4000...
閱讀 3298·2021-11-25 09:43
閱讀 2098·2021-09-22 10:02
閱讀 3363·2021-09-06 15:00
閱讀 2309·2019-08-30 15:56
閱讀 2364·2019-08-30 15:54
閱讀 3239·2019-08-30 14:14
閱讀 2274·2019-08-29 17:25
閱讀 2917·2019-08-29 17:16