重點摘要
為短片加上動畫效果時使用縮放轉換功能。如要防止子項在動畫期間拉長或偏移,可以對子項進行計數器縮放。
我們先前已張貼更新內容,說明如何建立高效能的視差效果和無限捲動工具。本文將詳細說明如何製作高效短片動畫。如要查看示範,請參閱 範例 UI 元素 GitHub 存放區。
以展開式選單為例:
有部分選項會成效較其他選項來得好。
不佳:在容器元素上建立寬度和高度動畫
您可以想像用一些 CSS 為容器元素的寬度和高度建立動畫。
.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 的作業。最新消息,請參閱轉譯效能指南,進一步瞭解轉譯程序的運作方式。
不佳:使用 CSS 裁剪或裁剪路徑屬性
除了使用 width
和 height
建立動畫之外,您也可以使用 clip
屬性 (現已淘汰) 為展開和收合效果製作動畫。或者,您也可以改用 clip-path
。但是,與 clip
相比,使用 clip-path
並受到全面支援。但 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
屬性,則該路徑所執行的元素必須指定絕對位置或固定位置,這可能會需要多一點疊加。
良好:為體重計加上動畫效果
此效果需要放大及縮小,因此您可以使用比例轉換。這才是好消息,因為改變的轉換不需要版面配置或繪製,而瀏覽器可以向 GPU 發出,也就是說,效果會加速,且更有可能達到 60fps。
但這個方法的缺點,如同大多數轉譯效能的缺點,就是需要進行一些設定。但這絕對值得!
步驟 1:計算開始與結束狀態
使用縮放動畫的方法時,首先要讀取元素,瞭解選單在收合和展開時所需的大小。在某些情況下,可能無法一氣呵成這些資訊,而您必須視情況切換某些類別,以便讀取元件的各種狀態。但是,如果您需要這麼做,請注意:getBoundingClientRect()
(或 offsetWidth
和 offsetHeight
) 會在樣式自上次執行後變更樣式時,強制瀏覽器執行樣式和版面配置傳遞。
function calculateCollapsedScale () {
// The menu title can act as the marker for the collapsed state.
const collapsed = menuTitle.getBoundingClientRect();
// Whereas the menu as a whole (title plus items) can act as
// a proxy for the expanded state.
const expanded = menu.getBoundingClientRect();
return {
x: collapsed.width / expanded.width,
y: collapsed.height / expanded.height
};
}
對於選單之類的項目,我們可以做出合理假設,讓其一開始會以自然的尺度 (1, 1) 開始顯示。此自然比例代表展開狀態,也就是說,您必須從縮減版本 (如上述計算) 建立動畫,一直到該自然縮放比例。
可是,這麼做可以調整選單內容,對吧?如下方所示,沒錯
該怎麼辦?您可以將 counter- 轉換套用至內容,因此舉例來說,如果容器縮減為正常大小的 1/5,您可以將內容「向上」調整為 5 倍,以免內容遭到壓縮。請留意以下兩點:
計數器轉換也是比例作業。由於也可以像容器上的動畫一樣加速,因此這是良好。您可能需要確保動畫的元素取得專屬的合成層 (可讓 GPU 提供協助),並為元素新增
will-change: transform
;如果需要支援舊版瀏覽器,則backface-visiblity: hidden
。必須按影格計算計數器轉換。這可能會變得較複雜,因為假設動畫是採用 CSS 格式,而且使用加/減速函式,就要為計數器轉換設定動畫效果時,必須反轉加/減速設定。不過,假設
cubic-bezier(0, 0, 0.3, 1)
並非顯而易見,例如:
你可能會想使用 JavaScript 以動畫效果呈現效果。之後,您就可以使用加/減速公式計算每影格的比例和計數器尺度值。任何以 JavaScript 為基礎的動畫缺點,是當主執行緒 (JavaScript 執行時) 處理其他工作時,會發生什麼事。簡單來說,您的動畫可能會延遲或完全停止,因為這並不適合使用者體驗。
步驟 2:即時建構 CSS 動畫
但一開始可能會產生奇怪的解決方法,就是使用自有的加/減速函式建立主要畫面格動畫,並將其插入頁面供選單使用。(非常感謝 Chrome 工程師 Robert Flack 特別指出這一點!)這麼做的主要好處是,能夠改變轉換的主要畫面格動畫可以在合成器上執行,也就是說,其不受主執行緒中的工作影響。
如要製作主要畫面格動畫,我們會逐步從 0 開始至 100,並計算元素及其內容所需的縮放值。接著,這些字串可繫結至字串,以樣式元素插入頁面中。插入樣式會導致「重新計算樣式」傳遞到頁面上,這是瀏覽器必須進行的額外工作,但系統只會在元件啟動時執行一次。
function createKeyframeAnimation () {
// Figure out the size of the element when collapsed.
let {x, y} = calculateCollapsedScale();
let animation = '';
let inverseAnimation = '';
for (let step = 0; step <= 100; step++) {
// Remap the step value to an eased one.
let easedStep = ease(step / 100);
// Calculate the scale of the element.
const xScale = x + (1 - x) * easedStep;
const yScale = y + (1 - y) * easedStep;
animation += `${step}% {
transform: scale(${xScale}, ${yScale});
}`;
// And now the inverse for the contents.
const invXScale = 1 / xScale;
const invYScale = 1 / yScale;
inverseAnimation += `${step}% {
transform: scale(${invXScale}, ${invYScale});
}`;
}
return `
@keyframes menuAnimation {
${animation}
}
@keyframes menuContentsAnimation {
${inverseAnimation}
}`;
}
既然如此,您可能會想知道 for 迴圈中的 ease()
函式。您可以使用這類方式,將 0 到 1 的值對應到簡化的等值。
function ease (v, pow=4) {
return 1 - Math.pow(1 - v, pow);
}
您也可以使用 Google 搜尋來繪製外觀。實用!如果您需要其他加/減速方程式,請查看 Tween.js by Soledad Penadés,其中包含整個堆積的堆積。
步驟 3:啟用 CSS 動畫
這些動畫在 JavaScript 中建立並製備至網頁後,最後一步是切換啟用動畫的類別。
.menu--expanded {
animation-name: menuAnimation;
animation-duration: 0.2s;
animation-timing-function: linear;
}
.menu__contents--expanded {
animation-name: menuContentsAnimation;
animation-duration: 0.2s;
animation-timing-function: linear;
}
這樣會執行上一個步驟中建立的動畫。由於現行的動畫已經簡化,時間函式必須設為 linear
,否則每個主要畫面格看起來都很奇怪!
如要將元素向下收合,有兩種選項:更新 CSS 動畫以反向執行 (而非向前)。這樣雖然沒問題,但動畫的「資產」反而會反轉,因此如果您使用緩解曲線,反向操作會變得「in」,而會變得較為緩慢。更合適的做法是建立第二組動畫來收合元素。建立這些項目的方式與展開主要畫面格動畫相同,但會使用已切換的開始和結束值。
const xScale = 1 + (x - 1) * easedStep;
const yScale = 1 + (y - 1) * easedStep;
進階版本:循環顯示
你也可以使用這項技巧製作圓形展開及收合動畫。
這些原則大致與先前版本相同,也就是您針對元素進行縮放後,對其直接子項進行反轉。在此範例中,向上縮放的元素有 50% 的 border-radius
,設為圓形,並由包含 overflow: hidden
的「另一個」元素包裝。也就是說,圓形在元素邊界外不會展開。
針對這個特定變化版本提供的警告說明:由於文字的比例和計數器縮放錯誤,導致 Chrome 在動畫播放期間的 DPI 螢幕出現模糊效果。如果您有興趣進一步瞭解相關資訊,請找出錯誤,並加上星號並加以追蹤。
您可以在 GitHub 存放區中找到循環展開效果的程式碼。
結論
您已經完成了,這是使用縮放轉換功能執行高效剪輯動畫的方法。在完美的世界中,可以看到剪輯動畫加速 (這是由 Jake Archibald 所製作的 Chromium 錯誤) 一樣,但在此之前,您應謹慎為 clip
或 clip-path
建立動畫,並絕對避免使用 width
或 height
動畫。
此外,使用網路動畫產生這類效果也很有幫助,因為這類動畫具有 JavaScript API,但如果您只建立 transform
和 opacity
的動畫,可以在合成執行緒上執行。遺憾的是,對網路動畫的支援不太完善,但您也可以採用漸進式增強功能 (如果有的話)。
if ('animate' in HTMLElement.prototype) {
// Animate with Web Animations.
} else {
// Fall back to generated CSS Animations or JS.
}
在這些變更之前,雖然您可以使用以 JavaScript 為基礎的程式庫進行動畫,但可能會發現製作 CSS 動畫並改用這種動畫,可獲得更可靠的效能。同理,如果應用程式已使用 JavaScript 處理動畫,則建議至少與現有程式碼集保持一致,才能改善使用效果。
如果您想查看這種效果的程式碼,請查看 UI Element 範例 GitHub 存放區。