
這期我們來完善上一期的影片庫,在 Animation 類中的 constructor 的引數,我們發現其他的引數都用上了,但是 timingFunction 我們是還沒有使用上的,這里我們就來一起處理這個問題,
timingFucntion 這個邏輯主要是用在 Animation 的 run 方法中,如果大家還記得之前的一篇講解 CSS 影片的文章,里面我們了解到三次貝塞爾曲線,
在三次貝塞爾曲線里面,它的 y 軸就是 progress(進度),而 x 軸就是 time(時間),一條三次貝塞爾曲線就會從 [0, 0] 坐標到 [1, 1] 坐標移動的軌跡,
我們的 timingFunction 也是這樣一個函式,傳入我們的時間進度,從而獲得一個三次貝塞爾曲線的移動軌跡的進度,
TimingFunction
TimingFunction 是一個關于 0 ~ 1 的 time(時間)的函式,通過三次貝塞爾計算回傳一個 0~1 的 progress(進度),
在 CSS 里面我們就有幾個庫,就比如 linear 這個 timingFunction,那么這里我們也去嘗試寫一些與三次貝塞爾曲線比較接近的 timingFunction,
Linear
首先我們來實作 Linear 這種影片曲線,這種曲線是相對比較好實作的,它就是一個自身不變的,一比一的 timingFunction,所以代碼也是非常的簡單:
export let linear = v => v;
實作三次貝塞爾函式
linear 種其實沒有任何的緩動的效果的,如果我們想實作三次貝塞爾曲線中的 ease、ease-in、ease-out 這樣的影片效果的話,我們就需要用到三次貝塞爾曲線中的 “牛頓積分法“ 去求一個時間點的進度值,
所以這里我們需要先實作三次貝塞爾曲線的函式來進行計算,
首先我們可以看看三次貝塞爾曲線的網站:

我們可以看到三次貝塞爾曲線是通過用 4 個引數來進行計算的,而這四個引數就是決定我們影片曲線的效果,
這里我們也不去詳細的分析和推論出三次貝塞爾曲線的計算方式了,我們可以直接從 C++ 的庫中把這個函式的代碼取出來,然后轉換這個代碼成 JavaScript 然后直接使用即可,
export function cubicBezier(p1x, p1y, p2x, p2y) {
const ZERO_LIMIT = 1e-6;
// Calculate the polynomial coefficients,
// implicit first and last control points are (0,0) and (1,1).
const ax = 3 * p1x - 3 * p2x + 1;
const bx = 3 * p2x - 6 * p1x;
const cx = 3 * p1x;
const ay = 3 * p1y - 3 * p2y + 1;
const by = 3 * p2y - 6 * p1y;
const cy = 3 * p1y;
function sampleCurveDerivativeX(t) {
// `ax t^3 + bx t^2 + cx t` expanded using Horner's rule
return (3 * ax * t + 2 * bx) * t + cx;
}
function sampleCurveX(t) {
return ((ax * t + bx) * t + cx) * t;
}
function sampleCurveY(t) {
return ((ay * t + by) * t + cy) * t;
}
// Given an x value, find a parametric value it came from.
function solveCurveX(x) {
let t2 = x;
let derivative;
let x2;
// https://trac.webkit.org/browser/trunk/Source/WebCore/platform/animation
// first try a few iterations of Newton's method -- normally very fast.
// http://en.wikipedia.org/wikiNewton's_method
for (let i = 0; i < 8; i++) {
// f(t) - x = 0
x2 = sampleCurveX(t2) - x;
if (Math.abs(x2) < ZERO_LIMIT) {
return t2;
}
derivative = sampleCurveDerivativeX(t2);
// == 0, failure
/* istanbul ignore if */
if (Math.abs(derivative) < ZERO_LIMIT) {
break;
}
t2 -= x2 / derivative;
}
// Fall back to the bisection method for reliability.
// bisection
// http://en.wikipedia.org/wiki/Bisection_method
let t1 = 1;
/* istanbul ignore next */
let t0 = 0;
/* istanbul ignore next */
t2 = x;
/* istanbul ignore next */
while (t1 > t0) {
x2 = sampleCurveX(t2) - x;
if (Math.abs(x2) < ZERO_LIMIT) {
return t2;
}
if (x2 > 0) {
t1 = t2;
} else {
t0 = t2;
}
t2 = (t1 + t0) / 2;
}
// Failure
return t2;
}
function solve(x) {
return sampleCurveY(solveCurveX(x));
}
return solve;
}
如果想詳細了解這段代碼的邏輯和推算程序,可以去了解代碼備注中的鏈接,
實作影片效果庫
好,我們有了 cubicBezier 這個函式,我們就可以通過使用它,實作 ease、ease-in、ease-out、ease-in-out 等影片曲線了,
我們可以通過三次貝塞爾曲線的官網,取得每個常用的影片效果的 4 個引數值,然后傳入 cubicBezier 這個函式即可獲得我們想要的進度值,
每個影片效果的方法實作如下:
Ease
export let ease = cubicBezier(0.25, 0.1, 0.25, 1);
Ease In
export let easeIn = cubicBezier(0.42, 0, 1, 1);
Ease Out
export let easeOut = cubicBezier(0, 0, 0.58, 1);
Ease In Out
export let easeInOut = cubicBezier(0.42, 0, 0.58, 1);
最后我們把所有這些影片曲線的函式都放入一個
ease.js的 JavaScript 檔案即可,這樣我們 timingFunction 庫就完成了,
使用 TimingFunction
有了 timingFunction 的庫,我們就可以在 animation-demo.js 中使用這些影片函式來給我們的元素加入影片效果,
在我們使用這個之前,我們 animation.js 中的 timingFunction 和 template 引數都是沒有給予默認值的,為了防止不必要的出錯,我們可以給他們一個默認值,
我們只需要在 Animation Class 里面加入兩行賦予引數默認值的邏輯即可:
constructor(object, property, startValue, endValue, duration, delay, timingFunction, template) {
timingFunction = timingFunction || (v => v);
template = template || (v => v);
this.object = object;
this.property = property;
this.startValue = startValue;
this.endValue = endValue;
this.duration = duration;
this.timingFunction = timingFunction;
this.delay = delay;
this.template = template;
}
接下來我們回到 animation-demo.js,并且通過 ease.js 來引入 ease 影片函式,
import {ease} from './ease.js';
然后在 Timeline 添加 Animation 的引數,傳入 ease 這個 timingFunction,
tl.add(
new Animation(
document.querySelector('#el').style,
'transform',
0,
500,
2000,
0,
ease,
v => `translate(${v}px)`
)
);
我們這個 ease 影片函式實作的影片與 CSS 中 transition 的 ease 是否是一樣的呢?為了證明我們實作了 CSS 中一樣的 ease 影片效果,我們建立多一個 div 并且給它賦予 CSS 的 transition,這樣我們就可以直觀看到他們之間是否是一樣的效果了,
首先在 animation.html 中加入這個 HTML 和 CSS 的代碼:
<style>
:root {
--bg-color: #0f0e18;
--sub-bg-color: #2d2f42;
--purple-color: fuchsia;
--blue-color: aqua;
}
body {
background: var(--bg-color);
}
.buttons {
color: var(--purple-color);
}
.purple {
color: var(--purple-color);
}
.blue {
color: var(--blue-color);
}
.box {
width: 100px;
height: 100px;
background-color: aqua;
margin-bottom: 1rem;
}
.ease-box {
background-color: fuchsia !important;
}
button {
background: transparent;
color: var(--blue-color);
border: 1px solid aqua;
padding: 0.5rem 1rem;
cursor: pointer;
transition: all 400ms ease;
}
button:hover {
background: var(--blue-color);
color: #333;
}
</style>
<body>
<div class="box" id="el"></div>
<div class="box ease-box" id="el2"></div>
<button id="pause-btn">Pause</button>
<button id="resume-btn">Resume</button>
<script src="./main.js"></script>
</body>
然后我們回到 animation-demo.js,給 el2 這個元素加入 transition 的屬性,
document.querySelector('#el2').style.transition = 'transform 2s ease';
document.querySelector('#el2').style.transform = 'translateX(500px)';
然后我們來看一下效果:

藍色的盒子就是我們自己撰寫的 ease 影片,而紫色的盒子就是 CSS 中的 ease 影片,
我們可以說基本上他們就是一致的,只不過 C++ 和 CSS 中三次貝塞爾曲線的計算有一點的差異導致影片有微小的不一樣,但是大致上是一摸一樣的,
重點是我們 JavaScript 實作的影片,是可以隨時突發暫停,也可以隨時觸發繼續播放的,但是 CSS 的影片是無法達到一樣的功能的,它只能一播放就播放到結束,
實作重置
最后我們就去把 Timeline 中的重置功能也給實作了,其實這個功能的邏輯非常簡單,它所需要用到的函式我們都有了,
- 首先需要先暫停這個影片
- 重置
startTime開始時間為當前時間 - 重置
PAUSE_TIME為 0 - 重置
PAUSE_START為 0 - 重置
ANIMATION為new Set() - 重置
START_TIMES為new Map() - 重置
TICK_HANDLER為 null
reset() {
this.pause();
this[PAUSE_TIME] = 0;
this[PAUSE_START] = 0;
this[ANIMATIONS] = new Set();
this[START_TIMES] = new Map();
this[TICK_HANDLER] = null;
}
就這樣我們就完成了一個比較完善的影片庫了,
對時間軸加入狀態管理
雖然說我們的影片庫和時間線的功能已經是非常完善了,但是其實里面還有一些存在的問題的,比如,我們沒有呼叫 pause 就直接呼叫了 resume,這有可能會出現一些問題,
所以我們要給這個庫安排一個狀態管理,讓這個類更具有健壯性,
初始化時
首先在 Timeline 實體化的時候,我們注入一個 Initiated 的狀態,代表我們 Timeline 是在 初始化完畢 狀態了,
constructor() {
this.state = 'Initiated';
this[ANIMATIONS] = new Set();
this[START_TIMES] = new Map();
}
開始時
在 Timeline 的 start 方法執行的時候,我們就可以判斷這個 Timeline 是否被初始化了,如果沒有直接斷開不繼續執行,當然我們這里也可以直接拋一個錯誤,不過這個根據我們 API 設計風格而定即可,
如果可以執行 Timeline 的啟動的話,我們就可以把狀態改為 Started,這樣我們就讓這個 Timeline 變成一個開始之后的狀態,
start() {
if (!this.state === 'Initiated') return;
this.state = 'Started';
/* ... */
}
暫停時
暫停狀態的判斷與開始狀態一樣,先判斷 Timeline 是否已經進入了開始狀態,如果沒有就直接推出執行,
如果可以執行,就把狀態更新為 Paused(已暫停),
pause() {
if (!this.state === 'Started') return;
this.state = 'Paused';
/* ... */
}
恢復時
先判斷是否在暫停狀態,如果不是就直接停止執行,否則更新狀態為 Started(開始狀態),
resume() {
if (!this.state === 'Paused') return;
this.state = 'Started';
/* ... */
}
重置時
重置是可以隨時執行的,并不需要任何的前天條件的,這里我們只需要把狀態更新為 Initiated 即可,
reset() {
this.pause();
this.state = 'Initiated';
/* ... */
}
最后的添加時是不需要任何的攔截和狀態更變的,所以 add 方法我們就可以不用動它了,
最后
在管理上來說,我們其實應該把 animation 相關的檔案都放入一個新的專案里面的,但是這里我們就不詳細做這部分了,同學自行進行整理即可,
這樣我們就把 animation library(影片庫)完整的實作了,
下一篇文章我們就會去把這個 animation 庫與我們的 carousel(輪播圖)結合來使用,最終用這個做一個完整的自定義組件庫,
我是來自《技術銀河》的三鉆,一位正在重塑知識的技術人,下期再見,
?? 三哥推薦
開源專案推薦
Hexo Theme Aurora
最近更新到了版本 1.3.0
謝謝各位的支持和持續的反饋和建議~

最近博主在全面投入開發一個可以 “邁向未來的” Hexo 主題,以極光為主題的博客主題,
如果你是一個開發者,做一個個人博客也是你簡歷上的一個亮光點,而如果你有一個超級炫酷的博客,那就更加是亮上加亮了,簡直就閃閃發光,
如果喜歡這個主題,可以在 Github 上給我點個 🌟 讓彼此都發光吧~
主題 Github 地址:https://github.com/auroral-ui/hexo-theme-aurora
主題使用檔案:https://aurora.tridiamond.tech/zh/

博主開始在B站直播學習,歡迎過來《直播間》一起學習,
我們在這里互相監督,互相鼓勵,互相努力走上人生學習之路,讓學習改變我們生活!
學習的路上,很枯燥,很寂寞,但是希望這樣可以給我們彼此帶來多一點陪伴,多一點鼓勵,我們一起加油吧! (? ?????)?
專欄推薦
小伙伴們可以查看或者訂閱相關的專欄,從而集中閱讀相關知識的文章哦,
-
?? 《2021年總結》 — 一個一線戰場中的開發者,回歸到學習的學堂中,一開始這個程序確實遇到了挺多困擾的,一開始無法靜心下來學習,因為學習底層的知識確實需要靜下心來學,但是堅持了一段時間后,又會發現自己會愛上學習,愛上深挖這些知識,
-
📖 《前端進階》 — 這里包含的文章學習內容需要我們擁有 1-2 年前端開發經驗后,選擇讓自己升級到高級前端工程師的學習內容(這里學習的內容是對應阿里 P6 級別的內容),
-
📖 《資料結構與演算法》 — 到了如今,如果想成為一個高級開發工程師或者進入大廠,不論崗位是前端、后端還是AI,演算法都是重中之重,也無論我們需要進入的公司的崗位是否最后是做演算法工程師,前提面試就需要考演算法,
-
📖 《FCC前端集訓營》 — 根據FreeCodeCamp的學習課程,一起深入淺出學習前端,穩固前端知識,一起在FreeCodeCamp獲得證書
-
📖 《前端星球》 — 以實戰為線索,深入淺出前端多維度的知識點,內含有多方面的前端知識文章,帶領不懂前端的童鞋一起學習前端,在前端開發路上童鞋一起燃起心中那團火🔥
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/275813.html
標籤:其他
