動畫的基本原理#
動畫是什麼#
動畫是通過快速連續排列彼此差異極小的連續圖像來製造運動錯覺和變化錯覺的過程。
—— 維基百科
- 快速
- 連續排列
- 彼此差異極小
- 製造 “錯覺” 的過程
動畫發展史#
如今的前端動畫技術已經普及
-
常見的前端動畫技術
- Sprite 動畫、CSS 動畫、JS 動畫、SVG 動畫和 WebGL 動畫
-
按應用分類
- UI 動畫、基於 Web 的遊戲動畫和動畫數據可視化
GIF、Flash 的出現,一度成為主流,也是在 00 年的前後,蘋果公司認為 Flash 會導致 CPU 的負載,耗電加快,宣布全面放棄 Flash,所有的蘋果設備電池壽命都顯著提高。而如今的 web 動畫主要由 CSS、JS 動畫為主。
計算機動畫#
計算機圖形學
計算機視覺的基礎,涵蓋點、線、面、體、場的數學構造方法。
- 幾何和圖形數據的輸入、存儲和壓縮。
- 描述紋理、曲線、光影等算法。
- 物體圖形的數據輸出(圖形接口、動畫技術),硬件和圖形的交互技術。
- 圖形開發軟件的相關技術標準。
計算機動畫 是計算機圖形學的分支,主要包含 2D、3D 動畫。無論動畫多麼簡單,始終需要定義兩個基本狀態,即開始狀態和結束狀態。沒有它們,我們將無法定義插值狀態,從而填補兩者之間的空白。
快速√ 連續排列 × 彼此差異極小 × 製造 “錯覺”×
可以看到,上面這張動畫只有快速,並沒有製造錯覺,這就不得不提到幀率這個概念了~~(打遊戲的這個概念應該都熟)~~
-
幀:連續變換的多張畫面,其中的每一幅畫面都是一幀。
-
幀率:用於度量一定時間段內的幀數,通常的測量單位是FPS(frame per second)。
-
幀率與人眼:一般每秒 10-12 幀人會認為畫面是連貫的,這個現象稱為視覺暫留。對於一些電腦動畫和遊戲來說,低於 30 FPS 會感受到明顯卡頓,目前主流的螢幕、顯卡輸出為 60FPS,效果會明顯更流暢。
那麼接下來,填補起始點和結束點之間的空白,嘗試讓動畫連貫。
空白的補全方式有以下兩種
- 補間動畫
- 傳統動畫中,主畫師繪製關鍵幀,交給清稿部門,清稿部門的補間動畫師補充關鍵幀進行交付
- (類比到這裡,前端動畫的補間動畫師由瀏覽器來擔任,如
@keyframes
,transition
)
- 逐幀動畫(Frame By Frame)
- 從詞語來說意味著全片每一幀逐幀都是純手繪。(如 css 的 steps 實現精靈動畫)
前端動畫分類#
css 動畫#
CSS 層疊樣式表 (Cascading Style Sheets),本身是一種樣式表語言,用來描述 HTML 或 XML(包括如 SVG、MathML、XHTML 之類的 XML 分支語言),而 CSS 中的 animation
屬性是 animation-name
,animation-duration
, animation-timing-function
,animation-delay
,animation-iteration-count
,animation-direction
,animation-fill-mode
和 animation-play-state
屬性的簡寫屬性形式。
animation-name#
animation-name
屬性指定應用的一系列動畫,每個名稱代表一個由@keyframes
定義的動畫序列,其值如下
none
(初始值)特殊關鍵字,表示無關鍵幀。可以不改變其他標識符的順序而使動畫失效,或者使層疊的動畫樣式失效。IDENT
標識動畫的字符串,由大小寫敏感的字母 a-z、數字 0-9、下劃線 (_) 和 / 或橫線 (-) 組成。第一個非橫線字符必須是字母,數字不能在字母前面,不允許兩個橫線出現在開始位置。
多個動畫定義使用逗號分隔開即可
/* Single animation */
animation-name: none;
animation-name: test_05;
animation-name: -specific;
animation-name: sliding-vertically;
/* Multiple animations */
animation-name: test1, animation4;
animation-name: none, -moz-specific, sliding;
/* Global values */
animation-name: initial
animation-name: inherit
animation-name: unset
animation-duration#
animation-duration
屬性指定一個動畫週期的時長。默認值為 0s,表示無動畫。
它的值為一個動畫週期的時長,單位為秒 (s) 或者毫秒 (ms),無單位值無效。也可以指定多個值,他的多個值與 animation-name 一一對應
** 注意:** 負值無效,瀏覽器會忽略該聲明,但是一些早期的帶前綴的聲明會將負值當作 0s。
/* Single animation */
animation-duration: 6s
animation-duration: 120ms
/* Multiple animations */
animation-duration: 1s, 15s
animation-duration: 10s, 30s, 230ms
animation-timing-function#
animation-timing-function
屬性定義 CSS 動畫在每一動畫週期中執行的節奏。可能值為一或多個,它的多個值也是與 animation-name 一一對應。本身 CSS 定義了一些緩動函數,我們可以調用這些緩動函數來達到緩入緩出的效果。
對於關鍵幀動畫來說,timing function 作用於一個關鍵幀週期而非整個動畫週期,即從關鍵幀開始開始,到關鍵幀結束結束。
定義於一個關鍵幀區塊的緩動函數 (animation timing function) 應用到該關鍵幀;另外,若該關鍵幀沒有定義緩動函數,則使用定義於整個動畫的緩動函數。
/* Keyword values */
animation-timing-function: ease;
animation-timing-function: ease-in;
animation-timing-function: ease-out;
animation-timing-function: ease-in-out;
animation-timing-function: linear;
animation-timing-function: step-start;
animation-timing-function: step-end;
/* Function values */
animation-timing-function: cubic-bezier(0.1, 0.7, 1.0, 0.1);
animation-timing-function: steps(4, end);
animation-timing-function: frames(10);
/* Multiple animations */
animation-timing-function: ease, step-start, cubic-bezier(0.1, 0.7, 1.0, 0.1);
/* Global values */
animation-timing-function: inherit;
animation-timing-function: initial;
animation-timing-function: unset;
animation-delay#
animation-delay
定義動畫於何時開始,即從動畫應用在元素上到動畫開始的這段時間的長度。(就是延遲多久開始)
0s
是該屬性的默認值,代表動畫在應用到元素上後立即開始執行。否則,該屬性的值代表動畫樣式應用到元素上後到開始執行前的時間長度;
** 定義一個負值會讓動畫立即開始。但是動畫會從它的動畫序列中某位置開始。** 例如,如果設定值為 - 1s,動畫會從它的動畫序列的第 1 秒位置處立即開始。
如果為動畫延遲指定了負值,但起始值是隱藏的,則從動畫應用於元素的那一刻起就獲取起始值。
animation-delay: 3s;
animation-delay: 2s, 4ms;
animation-iteration-count#
animation-iteration-count
定義動畫在結束前運行的次數 可以是 1 次也可以是無限循環.
-
infinite
無限循環播放動畫
-
<number>
動畫播放的次數;默認值為
1
。可以用小數定義循環,來播放動畫週期的一部分:例如,0.5
將播放到動畫週期的一半。不可為負值。
/* 值為關鍵字 */
animation-iteration-count: infinite;
/* 值為數字 */
animation-iteration-count: 3;
animation-iteration-count: 2.4;
/* 指定多個值 */
animation-iteration-count: 2, 0, infinite;
它的多值跟 duration 不同,它是在每個動畫開始和結束的時候切換自己的執行次數
animation-direction#
animation-direction
屬性指示動畫是否反向播放
-
normal
(默認值)每個循環內動畫向前循環,換言之,每個動畫循環結束,動畫重置到起點重新開始,這是默認屬性。
-
alternate
動畫交替反向運行,反向運行時,動畫按步後退,同時,帶時間功能的函數也反向,比如,
ease-in
在反向時成為ease-out
。計數取決於開始時是奇數迭代還是偶數迭代 -
reverse
反向運行動畫,每周期結束動畫由尾到頭運行。
-
alternate-reverse
反向交替, 反向開始交替
動畫第一次運行時是反向的,然後下一次是正向,後面依次循環。決定奇數次或偶數次的計數從 1 開始。
animation-direction: normal
animation-direction: reverse
animation-direction: alternate
animation-direction: alternate-reverse
animation-direction: normal, reverse
animation-direction: alternate, reverse, normal
animation-fill-mode#
animation-fill-mode
屬性設置 CSS 動畫在執行之前和之後如何將樣式應用於其目標。
/* Single animation */
animation-fill-mode: none;
animation-fill-mode: forwards;
animation-fill-mode: backwards;
animation-fill-mode: both;
/* Multiple animations */
animation-fill-mode: none, backwards;
animation-fill-mode: both, forwards, none;
-
none
(默認)當動畫未執行時,動畫將不會將任何樣式應用於目標,而是已經賦予給該元素的 CSS 規則來顯示該元素。這是默認值。
-
forwards
目標將保留由執行期間遇到的最後一個關鍵幀計算值。 最後一個關鍵幀取決於
animation-direction
和animation-iteration-count
的值(就是最後一個關鍵幀是什麼樣子後面就一直會是這個樣子) -
backwards
動畫將在應用於目標時立即應用第一個關鍵幀中定義的值,並在
animation-delay
期間保留此值。(這個很重要,delay 幾 s 進行的) 第一個關鍵幀取決於animation-direction
的值:animation-direction
first relevant keyframenormal
oralternate``0%
orfrom``reverse
oralternate-reverse``100%
orto
-
both
動畫將遵循
forwards
和backwards
的規則,從而在兩個方向上擴展動畫屬性。(就是上述二者兼有)
注意:當您在
animation-*
屬性上指定多個以逗號分隔的值時,它們將根據值的數量以不同的方式分配給animation-name
屬性中指定的動畫。 有關更多信息,請參閱設置多個動畫屬性值。
animation-play-state#
animation-play-state
屬性定義一個動畫是否運行或者暫停。可以通過查詢它來確定動畫是否正在運行。另外,它的值可以被設置為暫停和恢復的動畫的重放。恢復一個已暫停的動畫,將從它開始暫停的時候開始恢復,而不是從動畫序列的起點開始。
-
running
當前動畫正在運行。
-
paused
當前動畫已被停止。
/* Single animation */
animation-play-state: running;
animation-play-state: paused;
/* Multiple animations */
animation-play-state: paused, running, running;
/* Global values */
animation-play-state: inherit;
animation-play-state: initial;
animation-play-state: unset;
去看了看這個大佬的其他項目,都很有意思!#codevember - 19 - CSS Eggs (codepen.io)、Periodic Table of Elements - HTML/CSS (codepen.io)
transform API#
transform
屬性允許你旋轉,縮放,傾斜或平移給定元素。這是通過修改 CSS 視覺格式化模型的坐標空間來實現的。
transform-origin
指定原點的位置,默認的轉換原點是 center
。
transform
屬性可以指定為關鍵字值none
或一個或多個<transform-function>
值。
-
要應用的一個或多個 CSS 變換函數。 變換函數按從左到右的順序相乘,這意味著複合變換按從右到左的順序有效地應用。
-
scale
(縮放)注意其中心為transform-origin
(縮放)注意其中心為transform-origin
// 沿x軸縮小為50% transform: scale(0.5); // 沿x軸縮小為50%,沿y軸放大為之前的2倍 transform: scale(0.5, 2);
-
rotate
(旋轉) 將元素在不變形的情況下旋轉到原點周圍 (如transform-origin
屬性所指定) 。 移動量由指定角度定義;如果為正值,則運動將為順時針,如果為負值,則為逆時針 。 180° 的旋轉稱為點反射 (point reflection)。transform: rotate(30deg);
-
skew
(傾斜) 參數表示傾斜角度,單位 deg一個參數時表示水平方向的傾斜角度(ax);
兩個參數時表示水平、垂直(ax, ay)。
transform: skew(ax) transform: skew(ax, ay)
-
-
none
不應用任何變換。
** 注意:** 他只能轉換由盒模型定位的元素,根據經驗如果元素具有 display: block,則由盒模型定位元素。
transition
過渡動畫,在 dom 加載完成或 class 發生變化時觸發,這個屬性是 transition-property
,transition-duration
,transition-timing-function
和 transition-delay
的一個簡寫屬性。
/* Apply to 1 property */
/* property name | duration */
transition: margin-right 4s;
/* property name | duration | delay */
transition: margin-right 4s 1s;
/* property name | duration | timing function */
transition: margin-right 4s ease-in-out;
/* property name | duration | timing function | delay */
transition: margin-right 4s ease-in-out 1s;
/* Apply to 2 properties */
transition: margin-right 4s, color 1s;
/* Apply to all changed properties */
transition: all 0.5s ease-out;
/* Global values */
transition: inherit;
transition: initial;
transition: unset;
keyframe 實現動畫#
@keyframes
關鍵幀 @keyframes at-rule 規則通過在動畫序列中定義關鍵幀(或 waypoints)的樣式來控制 CSS 動畫序列中的中間步驟。和 轉換 transition 相比,關鍵幀 keyframes 可以控制動畫序列的中間步驟。
// 從左側滑入
@keyframes slidein {
from {
transform: translateX(0%);
}
to {
transform: translateX(100%);
}
}
//
@keyframes identifier {
0% { top: 0; }
50% { top: 30px; left: 20px; }
50% { top: 10px; }
100% { top: 0; }
}
舉個栗子:my CSS Animation pratice (codepen.io)
@keyframes identifier {
0% { top: 0; left: 0; }
50% { top: 60%; left: 60%;}
100% { top: 0; left: 0; }
}
@keyframes slidein {
from {
transform: translateX(0%);
}
to {
transform: translateX(100%);
}
}
body >div {
position: absolute;
display:flex;
align-items:center;
justify-content: center;
color: #fafafa;
background-color: #141414;
padding: 10px;
width:20%; height:20%;
/* 從左上到右下,持續5s,延遲1s,無限循環 */
/* animation: identifier 5s linear 1s infinite; */
/* 向右滑,持續1s,兩次 */
animation: slidein 1s 2;
}
總結一下:
css 動畫的優點:簡單、高效、聲明式的。不依賴於主線程,採用硬件加速(GPU),通過簡單的控制 keyframe animation 播放和暫停。
缺點:不能動態修改或定義動畫,內容不同的動畫無法實現同步,多個動畫彼此無法堆疊。
適用場景:簡單的 h5 活動 / 宣傳頁。
推薦庫:Animate.css、CSShake等。
svg 實現動畫#
svg 是基於 XML 的矢量圖形描述語言,它可以與 CSS 和 JS 較好的配合,實現 svg 動畫通常有三種方式:SMIL、JS、CSS
- SMIL:同步多媒體集成語言
- 結論∶兼容性不理想,這裡不過多討論,當然有 polyfill 的方案:https://github.com/ericwilligers/svg-animation
- 使用 JS 來操作 SVG 動畫自不必多說,目前也有很多現成的類庫。例如老牌的 Snap.svg 以及 anime.js,都能讓我們快速製作 SVG 動畫。當然,除了這些類庫,HTML 本身也有原生的 Web Animation 實現。使用 Web 8622Animation 也能讓我們方便快捷地製作動畫。這是老師的兩個栗子:
- 文字形變: https://codepen.io/jiangxiang/pen/MWmdjeY
- Path 實現寫字動畫: SVG 寫字動畫 (codepen.io)
第一個動畫的實現原理
文字溶解原理 - filter#
filter
屬性將模糊或顏色偏移等圖形效果應用於元素。濾鏡通常用於調整圖像,背景和邊框的渲染。基礎案例:https://codepen.io/jiangxiang/pen/XWeQGQK
- blur 逐漸變小,在 blur 快沒有的時候將其 opacity 設為 0 隱藏掉,就可以實現一個溶解效果
JS 筆畫原理 - stroke#
stroke-dashoffset、stroke-dasharray 配合使用實現筆畫效果。
stroke-dasharray
屬性可控制用來描邊的點劃線的圖案範式。它是一個數列,數與數之間用逗號或者空白隔開,指定短劃線和缺口的長度。如果提供了奇數個值,則這個值的數列重複一次,從而變成偶數個值。因此,5,3,2 等同於 5,3,2,5,3,2。
stroke-dashoffset
屬性指定了 dash 模式到路徑開始的距離。
老師的示例:stroke-dasharray&stroke-dashoffset (codepen.io)
// 5px實線 5px空白 x1y1 -> x2y2
<line stroke-dasharray="5, 5" x1="10" y1="10" x2="190" y2="10" />
// 5px實線 10px空白
<line stroke-dasharray="5, 10" x1="10" y1="30" x2="190" y2="30" />
// 10px實線 5px空白
<line stroke-dasharray="10, 5" x1="10" y1="50" x2="190" y2="50" />
// 5px實線 1px空白...
<line stroke-dasharray="5, 1" x1="10" y1="70" x2="190" y2="70" />
<line stroke-dasharray="1, 5" x1="10" y1="90" x2="190" y2="90" />
<line stroke-dasharray="0.9" x1="10" y1="110" x2="190" y2="110" />
<line stroke-dasharray="15, 10, 5" x1="10" y1="130" x2="190" y2="130" />
<line stroke-dasharray="15, 10, 5, 10" x1="10" y1="150" x2="190" y2="150" />
<line stroke-dasharray="15, 10, 5, 10, 15" x1="10" y1="170" x2="190" y2="170" />
// 總長度180 180實線 180空白(全是實線) 這個時候改變dashoffset的值就可以實現筆畫效果
<line stroke-dasharray="180" stroke-dashoffset的值就可以實現筆畫效果="0" x1="10" y1="190" x2="190" y2="190" />
直線這種比較簡單的可以直接知道其總長度進而通過初始化 dashoffset 實現筆畫效果,那麼不規則圖形?
通過 path.getTotalLength ();
path 路徑 - d屬性定義
大寫字母跟隨的是絕對坐標 x,y,小寫為相對坐標 dx,dyM/m 繪製起始點。
-
L/l 繪製一條線段。
C/c 為繪製貝塞爾曲線。
Z/z 將當前點與起始點用直線連接。 -
計算 path 的長度 - path.getTotalLength ();
-
計算 path 上某個點的坐標 - path.getPointAtLength (lengthNumber);
老師的例子:SVG 使用 stroke-dashoffset 和 stroke-dashoffset 實現筆畫效果 (codepen.io)
-
svg 動畫的優點∶通過矢量元素實現動畫,不同的螢幕下均可獲得較好的清晰度。可以實現一些特殊的效果:描字,形變,墨水擴散等。
-
缺點∶使用方式較為複雜,過多使用可能會帶來性能問題。
js 實現動畫#
JS 可以實現複雜的動畫,可以操作 css、svg 也可以操作 canvas 動畫 API 上進行繪製。
如何做選擇?#
CSS 實現
-
優點
- 瀏覽器會對 CSS3 動畫做一些優化,導致 CSS3 動畫性能上稍有優勢(新建一個圖層來跑動畫)
- CSS3 動畫的代碼相對簡單
-
缺點
-
動畫控制上不夠靈活
-
兼容性不佳
-
部分動畫無法實現(視差效果、滾動動畫)
-
-
對於簡單動畫都可以用 css 實現
JS 實現
-
優點
-
使用靈活,同樣在定義一個動畫的 keyframe 序列時,可以根據不同的條件調節若干參數(JS 動畫函數)改變動畫方式。(CSS 會有非常多的代碼冗餘)
-
對比與CSS 的 keyframe 粒度更粗,Css本身的時間函數是有限的,這塊 JS 都可做彌補。
-
CSS 很難做到兩個以上的狀態轉化(要麼使用關鍵幀,要麼需要多個動畫延遲觸發,再想到要對動畫循環播放或暫停倒序等,複雜度極高)
-
-
缺點
- 使用到 JS 運行時,調優方面不如 CSS 簡單,CSS 調優方式固定。
- 對於性能和兼容性較差的瀏覽器,CSS 可以做到優雅降級,而 **JS 需要額外代碼兼容。** 會影響打包後產物的體積。
總結:
- 當為 UI 元素採用較小的獨立狀態時,使用 CSS.
- 在需要對動畫進行大量控制時,使用 JavaScript。
- 在特定的場景下可以使用 SVG,可以使用 CSS 或 JS 去操作 SVG 變化。(比如上述的溶解、筆畫效果等)
實現前端動畫#
js 動畫函數封裝#
function animate({easing, draw, duration}) {
let start = performance.now(); // 取當前時間
return new Promise(resolve => {
requestAnimationFrame(function animate(time) {
let timeFraction = (time - start) / duration;
if(timeFraction > 1) timeFraction = 1;
let progress = easing(timeFraction);
draw(progress);
if(timeFraction < 1) {
requestAnimationFrame(animate);
} else {
resolve();
}
});
});
}
這個函數中首先通過performance.now()取到當前系統時間,它以一個恆定的速率慢慢增加,不會受到系統時間的影響以浮點數的形式表示時間,精度最高可達微秒級,不易被篡改。入參說明:
-
draw 繪製函數
-
可以將其想象為一隻畫筆,隨著函數執行,這個畫筆的函數會被反復的調用,並傳入當前執行的進度 progress,progress 取決於 easing 的值,如若為線性增加的則為 0~1。如:
const ball = document.querySelector('.ball'); const draw = (progress) => { ball.style.transform = `translate(${progress}px, 0)`; }
-
-
easing 緩動函數
-
緩動函數改變(或者說扭曲)動畫的時間,將其改為線性 / 非線性,或者多維度的。如:
easing(timeFraction) { return timeFraction ** 2; //timeFraction的平方 }
-
-
duration 持續時間 顧名思義,單位是毫秒
-
最後返回 Promise 的原因:
-
Promise 是一個對象,它代表了一個異步操作的最終完成或者失敗。
-
動畫可以是連續的,Promise 支持通過 then 函數或 await 進行順序調用,可以很容易地拿到這個動畫的終態
-
-
這個動畫函數實現一個有限時間的動畫封裝
-
**RequestAnimationFrame (rAF) ** vs SetTimeout vs Setlnterval
-
使用requestAnimationFrame!為什麼?
該內置方法允許設置回調函數以在瀏覽器準備重繪時運行。通常這很快,但確切的時間取決於瀏覽器。
setTimeout 和 setInterval 當頁面在後台時,根本沒有重繪,所以回調不會運行:** 動畫將被暫停並且不會消耗資源。** 參考:javascript - requestAnimationFrame loop not correct FPS - Stack Overflow重排:若渲染樹的一部分更新,且尺寸變化,就會發生重排。
重繪:部分節點需要更新,但不改變其他集合形狀。如改變某個元素的 visibility、outline、背景顏色等,就會發生重繪。
-
簡單動畫#
JS 執行動畫的核心思想
∆r = ∆v∆t
簡單理解:r 為距離,v 速度,t 是時間。通過比例系數縮放來保證動畫的真實感。
舉個例子:均速運動,老師的例子非常全面JS 封裝動畫函數 (codepen.io)(一定要去看看!)
const ball = document.querySelector('.ball');
const draw = (progress) => {
ball.style.transform = `translate(${progress}px, 0)`;
}
// 沿x軸均速運動
animate({
duration: 1000,
easing(timeFraction) {
return timeFraction * 100;
},
draw
})
- 重力:h = g * t ^2^
t^2^// 重力
const gravity = () => {
const draw = (progress) => { // 高度500
ball.style.transform = `translate(0, ${500 * (progress - 1)}px)`;
};
animate({
duration: 1000,
easing(timeFraction) {
return timeFraction ** 2; // t平方
},
draw,
});
};
- 摩擦力:時間變為 2t - t^2^
// 摩擦力
const friction = () => {
// 初始高度500px
const draw = (progress) => {
ball.style.transform = `translate(0, ${500 * (progress - 1)}px)`;
};
animate({
duration: 1000,
easing(timeFraction) {
// 初始速度系數為2
return timeFraction * (2 - timeFraction);
},
draw,
});
};
- 平拋 (x 軸均速,y 軸加速) 也就是說,y 軸類似重力中的 t^2^ 而 x 軸速度不變
// 平拋 x
const horizontalMotion = () => {
const draw = (progress) => {
ball.style.transform = `translate(${500 * progress.x}px, ${500 * (progress.y - 1)}px)`;
};
// 有兩個方向,沿著x軸均速運動,沿著y軸加速運動
animate({
duration: 1000,
easing(timeFraction) {
return {
x: timeFraction,
y: timeFraction ** 2,
}
},
draw,
});
};
其余的還有很多,再多的話就在 easing 返回的對象中加入新的屬性,如旋轉:
-
旋轉 + 平拋
// 旋轉 + 平拋 const horizontalMotionWidthRotate = () => { const draw = (progress) => { ball.style.transform = `translate(${500 * progress.x}px, ${500 * (progress.y - 1)}px) rotate(${2000 * progress.rotate}deg)`;// 這裡的2000也是比例系數 }; // 有兩個方向,沿著x軸均速運動,沿著y軸加速運動 animate({ duration: 2000, easing(timeFraction) { return { x: timeFraction, y: timeFraction ** 2, rotate: timeFraction, } }, draw, }); };
-
拉弓(x 軸均速,y 軸初始速度為負的均加速)
// 拉弓 const arrowMove = () => { // 抽象出來,初始值為2,到某個臨界點變為正的1並且均速增加 const back = (x, timeFraction) => { return Math.pow(timeFraction, 2) * ((x + 1) * timeFraction - x); } const draw = (progress) => { ball.style.transform = `translate(${200*progress.x}px, ${ - 500 * progress.y}px)`; }; animate({ duration: 1000, easing(timeFraction) { return { x: timeFraction, y: back(2, timeFraction), }; }, draw, }); };
-
貝塞爾曲線 cubic-bezier(0,2.11,1,.19) ✿ cubic-bezier.com、Animated Bézier Curves - Jason Davies
- ps:講到這裡開始逐漸硬核起來了,tql
// 貝塞爾 const bezier = () => { const bezierPath = (x1, y1, x2, y2, timeFraction) => { const x = 3 * x1 * timeFraction * (1 - timeFraction) ** 2 + 3 * x2 * timeFraction ** 2 * (1 - timeFraction) + timeFraction ** 3; const y = 3 * y1 * timeFraction * (1 - timeFraction) ** 2 + 3 * y2 * timeFraction ** 2 * (1 - timeFraction) + timeFraction ** 3; return [x, y]; } const draw = (progress) => { const [px, py] = bezierPath(0.2, 0.6, 0.8, 0.2, progress); // 實際繪製在兩個維度上 ball.style.transform = `translate(${300 * px}px, ${-300 * py}px)`; } animate({ duration: 2000, easing(timeFraction) { return timeFraction * (2 - timeFraction); }, draw, }); }
複雜動畫#
-
彈跳小球(緩動函數實現 / 自動衰減實現)
-
直接去看老師的例子:JS 封裝動畫函數 (codepen.io),自動衰減這裡填了之前的一個坑:為什麼要使用 Promise,在每次執行完畢是,會將句柄再交給上面的函數判斷是否有速度的衰減,直到速度為 0 的時候會自動結束。
-
自動衰減:更加複雜
-
椭圓運動
-
套公式即可,x = a*cos (a),y = b * sin (a)
// 椭圓 const ellipsis = () => { const draw = (progress) => { const x = 150 * Math.cos(Math.PI * 2 * progress); const y = 100 * Math.sin(Math.PI * 2 * progress); ball.style.transform = `translate(${x}px, ${y}px)`; } animate({ duration: 2000, easing(timeFraction) { return timeFraction * (2 - timeFraction); }, draw, }); };
-
相關實踐資源#
動畫代碼示例:
- CodePen 可以提供很多設計靈感
- CodeSandbox 方便引入 SDK
設計網站:
動畫製作工具 (一般都是 UE、UI 同學使用):
- 2D : Animate CC、After Effects
- 3D 4D、Blender、Autodesk Maya
SVG:
-
Snap.SVG - 現代 SVG 圖形的 JavaScript 庫
-
Svg.js - 用於操作和動畫 SVG 的輕量級庫。
JS:
-
GSAP - JavaScript 動畫庫。
-
TweenJS - 一個簡單但功能強大的 JavaScript 補間 / 動畫庫。CreateJS 庫套件的一部分。
-
Velocity - 加速的 JavaScript 動畫。
CSS:
- Animate.css - CSS 動畫的跨瀏覽器庫。像一件簡單的事情一樣容易使用。
canvas:
-
EaselJS - EaselJS 是一個用於在 HTML5 中構建高性能交互式 2D 內容的庫
-
Fabric.js - 支持動畫的 JavaScript 畫布庫。
-
Paper.js - 矢量圖形腳本的瑞士軍刀
-
Scriptographer - 使用 HTML5
Canvas 移植到 JavaScript 和瀏覽器。
-
Pixijs –使用最快、最靈活的 2D WebGL 渲染器創建精美的數字內容。
在實際工作中往往是將 UI 給的動畫幀 / 設計文件轉化為代碼
-
需要完全前端自己設計自己開發時:
- 使用已封裝好的動畫庫,從開發成本和體驗角度出發進行取舍
-
設計不是很有空時:
- 清晰度、圖片格式可以指定,動畫儘量給出示意或者相似案例參考。索要精靈圖資源等需要幫忙壓縮。(移動端的資源適配等)
-
設計資源充足時:
-
要求設計導出 lottie 格式文件
Lottie 是可應用於 Android, ios, Web 和 Windows 的庫,
通過 Bodymovin 解析 AE 動畫,並導出可在移動端和 web 端渲染動畫的 json 文件。import lottie from 'lottie-web' ; this.animation = lottie.loadAnimation({ container: this.animationRef.current, renderer: 'svg', loop: false, autoplay: false, aninationData: dataJson, path: URL, });
-
優化#
-
性能角度
- 重點:減少重繪、重排,這是整個環節中最為耗時的兩環。
頁面渲染的一般過程為 JS -> CSS -> 計算樣式 --> 佈局 -> 繪製 -> 渲染層合併。
其中,Layout (重排) 和 Paint (重繪) 是整個環節中最為耗時的兩環,所以我們儘量避免這兩個環節。從性能方面考慮,最理想的渲染流水線是沒有佈局和繪製環節的,只需要做渲染層的合併即可。
- 通過CSS Triggers可以查詢 CSS 屬性及其影響的環節
建議#
- 在實際的應用裡,最為簡單的一個注意點就是,觸發動畫的開始不要用 display屬性值,因為它會引起 Layout、Paint 環節,通過切換類名就已經是一種很好的辦法。
ps:學到了!這就去改改
- CSS3 硬件加速又叫做 GPU 加速,是利用 GPU 進行渲染,減少 CPU 操作的一種優化方案。由於 GPU 中的 transform 等 CSS 屬性不會觸發 repaint,所以能大大提高網頁的性能。CSS 中的以下幾個屬性能觸發硬件加速:
- transform
- opacity
- filter
- Will-change
- 如果有一些元素不需要用到上述屬性,但是需要觸發硬件加速效果,可以使用一些小技巧來誘導瀏覽器開啟硬件加速。
- - 算法優化
- 線性函數代替真實計算 - 幾何模型優化
- 碰撞檢測優化 622
- 內存 / 緩存優化 - 離屏繪製
總結感想#
今天的課也非常的硬核,介紹了前端動畫的基本原理、前端動畫的分類和如何實現前端的動畫,並介紹了相關資源與實踐方法。讓我對前端動畫有了更深刻的了解,最後給出的資源推薦也很有幫助~
本文引用的內容大部分來自蒋翔老師的課、MDN(CSS 動畫屬性的介紹翻了半天 MDN,上面寫的非常全面並且有很生動的例子,推薦一看)