大前端開發中經常會遇到動畫的開發,那麼什麼是動畫?在物理學中運動就是研究物體在時間維度和空間維度上改變的現象,所以動畫也一樣,動畫主要研究2個因素,發生運動物體的時間和空間。
Web前端開發中的動畫
在 Web 前端開發中實現動畫有2種方式。要麼依靠 CSS 實現動畫,要麼依靠 JS 控制實現動畫。
CSS 實現動畫
首先要說 CSS 中的4個概念:animation、transition、transform、translate
屬性 | 含義 |
---|---|
transition(過度動畫) | 用於設定元素的樣式過渡效果,和 animation 有類似的效果,但存在使用場合有著較大差別 |
transform(變形) | 用於設定元素的旋轉、位移、縮放。和設定元素的動畫並沒直接關係,就跟寫 css 屬性一樣 |
translate(移動) | 用於設定元素的位置,就是 transform 的一個屬性 |
animation(動畫) | 用於設定怨毒的動畫屬性,它是一個簡寫,有6個屬性值 |
transition
字面意思,過渡是指元素從屬性a的某個值過渡到屬性a的另一個值,這就是一個狀態的改變,但是需要一個條件來觸發從而發生這種轉變,比如 &:hover,&:checked,&:focus、媒體查詢或者 JS
#box {
height: 100px;
width: 100px;
background: green;
transition: transform 1s ease-in 1s;
}
#box:hover{
transform: rotate(180deg) scale(0.5,0.5);
transform: translateX(100px);
transform: translateY(100px) translateX(100px) scale(0.5, 0.5);
}
<div id="box"></div>
複製程式碼
分析:給 div 新增了一個過渡動畫,動畫指定了 transform 動畫,觸發時機為當滑鼠移上去的時候。因此當滑鼠移入的時候元素的 transform 屬性發生變化,那麼這個時候觸發了 transition 動畫,當滑鼠移除的時候也產生了 transform 的變化,因此還是會觸發 transition,產生動畫。 上面設定了3個 transform 只有最後一個生效 因此 transition 產生動畫的條件是設定的 property 發生變化,這種動畫的特點是需要一個驅動力去觸發。因此就存在一些缺點:
- 需要事件觸發,沒法在網頁載入時自動發生
- 是一次性的,不能重複發生,除非再次觸發
- 只可以定義開始狀態和結束狀態,不能定義中間狀態,因此沒有豐富的動畫空間
- 一條 transition 規則,只能定義一個屬性的變化,不能涉及多個屬性
語法:transition:property duration timing-function delay
屬性 | 含義 |
---|---|
transition-property | 規定設定過渡效果的 css 屬性名稱 |
transition-duration | 規定完成過渡效果需要時間 |
transition-timing-function | 規定速度效果的速度曲線 |
transition-delay | 規定動畫效果何時開始 |
animation
animation 總體來說是對 transition 的增強,不再受限於觸發時機和動畫的屬性值。
.box {
height: 100px;
width: 100px;
border: 15px solid black;
animation: changebox 4s ease-in-out 1s 1 alternate running forwards;
}
.box:hover {
animation-play-state: paused;
}
@keyframes changebox {
10% {
background: red;
}
50% {
width: 80px;
}
70% {
border: 15px solid yellow;
}
100% {
width: 180px;
height: 180px;
}
}
<div class="box"></div>
複製程式碼
animation: animation-name, animation-duration, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, animation-fill-mode
屬性 | 含義 |
---|---|
animation-name | 用來呼叫@keyframes定義好的動畫,與@keyframes定義的動畫名稱一致 |
animation-duration | 指定元素播放動畫所持續的時間 |
animation-timing-function | 指定速度效果的速度曲線,是針對每一個小動畫所在時間範圍內的變化頻率 |
animation-delay | 定義在執行動畫之前的等待時間 |
animation-iteration-count | 定義動畫的播放次數,可選具體次數或者無限次(infinite) |
animation-direction | 設定動畫播放方向:normal(按時間軸順序)、alternate(輪流,即來回往復進行) |
animation-play-state | 控制元素的播放狀態:running(繼續)、paused(暫停) |
animation-fill-mode | 控制動畫結束後元素的樣式,有4個值: none(回到動畫之前的狀態)、forwards(元素停留在動畫結束後的狀態)、backwords(動畫回到第一幀的狀態)、both(根據 animation-direction 輪流應用 forwards 和 backwords 規則)。注意與 iteration-count 不要衝突 |
總結:單個動畫效果、簡單的由 transtion 實現,複雜的用 animation 實現。animation 出現後市面上出現了很多這種 css 動畫庫,其中我在使用 animate.css 推薦小夥伴們使用下
JS 實現動畫
大家用 JS 寫動畫立馬想到的是 setTimeout 和 setInterval,但是較好的動畫體驗是保持在 60fps 最好,上面的2個 api 由於會受到 runloop 的影響,並不會特別準時,JS 有個 requestAnimationFrame api 可以保持動畫在 60fps, JS 實現動畫的本質就是控制元素在時間和空間上的變化的研究。
假如要實現一個勻速直線動畫,讓一個 div 在 3秒內在水平方向上從向由右移動500px。那麼如何實現,先從物理問題上解決吧。
總時間: 3s 總位移: 500px 那麼每秒移動多少 500px/3s 我們設計一個 JS 函式。4個引數, 屬性開始值,屬性結束值,動畫執行時間,回撥函式
大體思路是:外界傳入上面4個引數,我們可以記錄函式呼叫剛開始的時刻也就是開始時間(start),然後通過 performance.now() 拿到當前時間(now),然後 period = (now-start) 就是經過的時間。然後通過 period/time 就是時間的進度百分比,拿這個百分比再去乘以總的屬性值差就是當前的屬性值,然後將計算結果實時呼叫回撥函式(這個回撥函式就是指定這個屬性值如何應用到動畫元素上)
/**
* 執行補間動畫方法
*
* @param {Number} start 開始數值
* @param {Number} end 結束數值
* @param {Number} time 補間時間
* @param {Function} callback 每幀回撥
* @param {Function} timing 速度曲線,預設勻速
*/
function animate(start, end, time, callback, timing = t => t) {
let startTime = performance.now() // 設定開始的時間戳
let period = end - start // 拿到數值差值
// 建立每幀之前要執行的函式
function loop() {
liveAnimationFunction = requestAnimationFrame(loop) // 下一呼叫每幀之前要執行的函式
const passTime = performance.now() - startTime // 獲取當前時間和開始時間差
let per = passTime / time // 計算當前已過百分比
if (per >= 1) { // 判讀如果已經執行
per = 1 // 設定為最後的狀態
cancelAnimationFrame(raf) // 停掉動畫
}
const pass = period * timing(per) // 通過已過時間百分比*開始結束數值差得出當前的數值
callback(pass)
}
let liveAnimationFunction = requestAnimationFrame(loop) // 下一陣呼叫每幀之前要執行的函式
}
function doMove(easing) {
animate(0, 100, 1000, move.bind(null, document.querySelector(`#${easing}Box`)), EasingFunctions[easing])
}
function move(box, value) {
box.style.transform = `translateX(${value}px)`
}
複製程式碼
Native 端動畫(iOS為例)
其實動畫的本質就是元素時間和空間上發生變化的研究。在 web 前端如此,在 native 端也是如此,不過就是換了一些 api 如此。
舉個例子,就拿上面所說的水平位移為例,下面給 iOS 的原生程式碼
//CALayer 層動畫
CABasicAnimation *positionAnimation = [CABasicAnimation animation];
//指定動畫路徑是水平方向x軸
positionAnimation.keyPath = @"position.x";
//指定位移距離
positionAnimation.toValue = @1000;
//下面2行程式碼讓動畫停留在動畫結束的位置
positionAnimation.fillMode = kCAFillModeForwards;//(效果完全等同於 css 中的 animation-fill-mode屬性)
positionAnimation.removedOnCompletion = NO;
[self.animationView.layer addAnimation:positionAnimation forKey:nil];
複製程式碼
css 中的 animation-fill-mode(控制動畫結束後元素的樣式,有4個值: none(回到動畫之前的狀態)、forwards(元素停留在動畫結束後的狀態)、backwords(動畫回到第一幀的狀態)、both(根據 animation-direction 輪流應用 forwards 和 backwords 規則))和 native(iOS)端的 fillMode 屬性一致。
下面提出 iOS 端的 fillMode 取值選項
/* `fillMode' options. */
CA_EXTERN CAMediaTimingFillMode const kCAFillModeForwards
API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
CA_EXTERN CAMediaTimingFillMode const kCAFillModeBackwards
API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
CA_EXTERN CAMediaTimingFillMode const kCAFillModeBoth
API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
CA_EXTERN CAMediaTimingFillMode const kCAFillModeRemoved
API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
複製程式碼
看得出來 fillMode web 端和 native 端是一模一樣的。
再舉個例子,平時我們有可能畫一根橫線,在 web 端 和 native 端都存在 view 這樣的概念,在 web 端可能會是一個 div(高度設定為1)或者是 canvas 實現(canvas 拿到當前上下文、繪製路徑、關閉路徑、填充顏色)。在 native 端也一樣,開啟繪圖上下文、拿到上下物件、繪製路徑、上色、關閉上下文。
所以其他具體的例子也就不舉了,本質上大前端的所有動畫乾的事情都一樣,所以我們需要處理好時間和位置的關係。