構建高性能展開&收縮動畫
前言
新起點。今日早讀文章由@杜夢傑翻譯授權分享。
正文從這開始~
TL;DR
進行動畫操作時使用scale變換(即transform: scale)。通過限制縮放來阻止子元素在動畫過程中變形。
我們之前已經發布過高性能視差滾動和無限滾動效果的文章。本文將討論如何創建高性能的裁剪動畫(Github repo)。
以展開菜單為例:
GIF/34K
某些製作展開菜單的方法比其他方法更加高性能。
Bad: 動畫操作容器元素的寬高
你可以想到用CSS來操作容器元素的寬度width和高度height。
.menu{
overflow:hidden;
width:350px;
height:600px;
transition:width 600ms ease-out,height 600ms ease-out;
}
.menu--collapsed{
width:200px;
height:60px;
}
這種方法的問題是要求動畫操作寬度width和高度height。這些屬性要求每幀都要計算布局並繪製結果,這是非常耗費性能的並且通常會使幀率低於60fps。如果你不了解這些知識,可以閱讀我們的渲染性能Rendering Performance指導,以便更好的了解渲染過程。
Bad: 使用 CSS clip 或 clip-path 屬性
一種替代方法是使用clip屬性(不贊成使用)來實現展開和摺疊效果。如果你喜歡的話也可以使用clip-path。但是clip-path的支持性比clip差。但是不建議使用clip屬性。是很糾結,但是不要失望,這並不是你想要的解決方法。
.menu{
position:absolute;
clip:rect(0px 112px 175px 0px);
transition:clip 600ms ease-out;
}
.menu--collapsed{
clip:rect(0px 70px 34px 0px);
}
儘管這種方法比操作寬度width和高度height更好,但是缺點是仍舊會觸發重繪。同時,如果你使用clip屬性,要求元素必須是絕對定位absolute或固定定位fixed,這也會造成一些額外的問題。
Good: 動畫縮放
由於這個效果涉及到元素的縮放,你可以使用scale變換。改變transform並不要求改變布局或重新繪製,這是極好的,並且瀏覽器可以把這些改變交給GPU來處理,這就意味著變換效果會被加速並很有可能達到60fps。
這種方法的缺點嘛,就和渲染性能Rendering Performance的很多東西一樣,需要一些準備工作。但絕對是值得的!
Step 1: 計算出初始狀態和結束狀態
使用這種縮放動畫的方法,第一步就是要知道元素展開和收縮時的尺寸。有些情況下你不能一次就得到所有信息,此時你需要切換不同的類來獲取元素的不同狀態。如果你這樣做的話,需要注意一點:如果與上次運行相比,樣式style發生了改變,getBoundingClientRect()(或offsetWidth和offsetHeight)會強制瀏覽器更新樣式和布局styles and layout。
functioncalculateCollapsedScale(){
// The menu title can act as the marker for the collapsed state.
constcollapsed=menuTitle.getBoundingClientRect();
// Whereas the menu as a whole (title plus items) can act as
// a proxy for the expanded state.
constexpanded=menu.getBoundingClientRect();
return{
x:collapsed.width/expanded.width,
y:collapsed.height/expanded.height
}
}
如果操作目標是菜單這樣的對象,我們可以合理假設它的初始狀態就是自然比例(1,1)。這個自然比例代表展開狀態,這意味著需要從縮小的狀態(上面計算出來的)回到自然比例。
但是,這種方法也會縮放菜單的內容。如下圖所示:
GIF/150K
我們應該怎麼做呢?你可以為內容應用counter-transform,例如容器縮小到原始尺寸的1/5,你可以把內容放大5倍來避免內容被擠壓。
有兩點需要注意:
counter-transform也是一種縮放操作。就像容器上的動畫一樣,它也可以被加速。需要確保動畫操作的元素有自己的compositor layer,為了這一點可以為元素添加will-change: transform,如果你需要支持舊版瀏覽器,添加backface-visibility: hiddden。
counter-transform每一幀都必須計算。這裡就有點棘手了,假設動畫是在CSS中且使用了緩動函數easing function,在進行counter-transform操作時緩動函數本身也需要抵消。但是,計算出反曲線,例如cubic-bezier(0, 0, 0.3, 1),並不是那麼簡單。
使用JavaScript來實現這個動畫效果確實很誘人。畢竟,這樣的話你就可以使用一個緩動方程來計算出每一幀縮放和反縮放scale and counter-scale的值。當主線程忙碌時就會體現出所有基於JavaScript的動畫的缺點,你的動畫會阻塞在一起,這對用戶體驗很不好。
Step 2: 動態創建CSS動畫
解決方法看起來可能有些奇怪,就是使用緩動函數動態的創建關鍵幀動畫keyframed animation並注入到頁面中讓菜單使用。這種方法的主要好處是關鍵幀動畫實現的變形可以運行在compositor上,不會被主線程上的任務所干擾。
為了製作關鍵幀動畫,我們計算出從0%到100%之間元素及其內容所需要的縮放值。這些關鍵幀動畫可以壓縮為一個字元串作為一個style element注入到頁面中。注入樣式會導致頁面重新計算樣式Recalculate Styles,這個額外工作是瀏覽器必須做的,但只會在組件啟動時運行一次。
functioncreateKeyframeAnimation(){
// Figure out the size of the element when collapsed.
let{x,y}=calculateCollapsedScale();
letanimation= ;
letinverseAnimation= ;
for(letstep=;step
// Remap the step value to an eased one.
leteasedStep=ease(step/100);
// Calculate the scale of the element.
constxScale=x+(1-x)*easedStep;
constyScale=y+(1-y)*easedStep;
animation+=`${step}% {
transform: scale(${xScale},${yScale});
}`;
// And now the inverse for the contents.
constinvXScale=1/xScale;
constinvYScale=1/yScale;
inverseAnimation+=`${step}% {
transform: scale(${invXScale},${invYScale});
}`;
}
return`
@keyframes menuAnimation {
${animation}
}
@keyframes menuContentsAnimation {
${inverseAnimation}
}`;
}
你可能會對for循環里的ease()函數非常好奇。你可以使用類似這樣的函數來使從0到1的值轉化為對應的過渡值。
functionease(v,pow=4){
return1-Math.pow(1-v,pow);
}
點擊展開全文
※BATJ 前端面試的 5 大關鍵點,你 Get 到了嗎?
※坦然面對:應對前端疲勞
※JSON schema與表單驗證
※4種使用webpack提升vue應用的方式
TAG:前端早讀課 |
※高性能電磁波屏蔽材料研究取得新進展
※潛心研究更高性能動力電池
※輕奢高性能輕薄本已成為市場主力 英特爾創新技術推動形態變革
※高性能銅網格柔性透明電極研究取得新進展
※高性能、高可用平台架構的演變過程
※距離柔性電視與高性能的可穿戴智能設備更進一步
※高性能導熱複合材料研究獲系列進展
※高性能纖維材料,構建現代科技的骨架
※移動固態硬碟好用處:改造高性能電腦
※AI驅動的山石網科 用高性能與高智能不斷實現安全進化
※單伺服器高性能模式
※科研人員開發出高性能鎂電池用凝膠聚合物電解質
※運用實時可視化與高性能渲染技術打造高品質影視內容
※IMEC使用3D列印技術研發出聚合物噴洒式晶元冷卻器,性能和體積均大幅優於傳統方法,為高性能晶元提供更多散熱方案
※研究人員成功製備仿蜘蛛絲結構的高性能導電水凝膠纖維
※大眾用量子計算開發下一代高性能電動汽車電池
※全球高性能計算髮展態勢分析
※我國高性能纖維行業的發展現狀及展望簡介
※電極「牽手」電解液協同增強實現高性能超級電容器
※可用於製作高性能新型探測器的人造光學材料