大前端動畫

杭城小劉發表於2018-10-12

大前端開發中經常會遇到動畫的開發,那麼什麼是動畫?在物理學中運動就是研究物體在時間維度和空間維度上改變的現象,所以動畫也一樣,動畫主要研究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 發生變化,這種動畫的特點是需要一個驅動力去觸發。因此就存在一些缺點:

  1. 需要事件觸發,沒法在網頁載入時自動發生
  2. 是一次性的,不能重複發生,除非再次觸發
  3. 只可以定義開始狀態和結束狀態,不能定義中間狀態,因此沒有豐富的動畫空間
  4. 一條 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 端也一樣,開啟繪圖上下文、拿到上下物件、繪製路徑、上色、關閉上下文。

所以其他具體的例子也就不舉了,本質上大前端的所有動畫乾的事情都一樣,所以我們需要處理好時間和位置的關係。

相關文章