);
class Page extends React.Component {
handleChangeX = () => {
this.setState({
x: storeX.getX()
})
}
componentDidMount = () => {
storeX.subscribe(this.handleChangeX)
}
render = () =>
}
const startAnimation = (beginPos = 0, endPos = 300, duration = 5000, frameTime = 17) => {
const now = performance.now();
const loop = () => {
const passedTime = performance.now() - now;
const distance = endPos - beginPos;
const currX = distance/duration*passedTime + beginPos;
storeX.changeX(currX);
}
setTimeout(loop, frameTime);
};
reactDOM.render(
, document.body)
有沒有覺得很棒!但仍然有優化的空間。動畫是源自現實世界的,人類早已習慣了一個變速運動的物理環境,這樣的一個勻速動畫會讓人相對感覺不適。為了優化用戶體驗,React Motion 使用了一種常見的變速運動 —— 彈簧運動。
React Motion 緩動原理剖析
React Motion 使動畫看起來像一個彈簧那樣(一個有空氣阻力的彈簧,如果沒有空氣阻力,彈簧就會不停地做簡諧運動了)。大家可以嘗試使用 React Motion 的spring-parameters-chooser,配置一個合適的勁度系數和空氣阻力。彈簧動畫可以使網站增添一些俏皮的元素,讓用戶體驗起來更加舒暢!
下面就讓我們進入主題,開始解讀 React Motion 的緩動過程。
先模擬彈簧的物理規律,實現彈簧動畫。
假設有一個彈簧,彈簧上綁了一個砝碼,回到初中物理,根據胡克定律,砝碼的受到彈簧的拉力為:
$$ F_{spring} = kvarDelta{x} (k為彈簧的勁度系數)$$
我們假設該砝碼受到的空氣阻力 kdamping 與砝碼當前的速度 vt 呈正相關,其中阻尼系數為 kdamping 。
對砝碼進行受力分析得:
$$ F = F_{spring} - F_{damping} = k_{spring}varDelta{x} - k_{damping} imes v_{t} $$
設 at 為砝碼當前加速度,得:
$$ F = ma_t $$
設 v" 和 x" 分別為經過 $$ dt $$ 時間后,砝碼新的速度和位移,得:
$$ a_t = lim_{dt o 0} frac{dv}{dt} = lim_{dt o 0} frac{v^{"} - v_t}{dt} $$
$$ v_t = lim_{dt o 0} frac{dx}{dt} = lim_{dt o 0} frac{x^{"} - x_t}{dt} $$
即:
$$ v^{"} = lim_{dt o 0} a_t*d_t + v_t $$
$$ x^{"} = lim_{dt o 0} v_t*d_t + x_t $$
我們拿到了計算新狀態的公式,但是 dt 是無限趨近于 0,怎么去模擬這個無限趨近于 0 呢?
現在只知道,當 dt 越趨近于 0 時,等式兩邊的值越接近(極限的單調有界準則可證)。可以把 dt 設為一個非常小的常量,雖然會造成一定的誤差,但是不足為慮,只要騙過人類的眼睛就可以了。
這樣我們就可以計算得出 v" 和 x" 。對以上過程不斷重復,就能計算出任意時刻的位移和速度。
這是個通用的模擬物理規律的緩動過程,是否讓你茅塞頓開?看一個同樣的模擬物理規律的動畫,有沒有手癢?
但是,原諒我又說了 “但是”,如果我們要用 raf 實現這個緩動的話,raf 不能設置 callback 的延遲時間,而我們的 dt 是一個固定的非常小的常量。這種情況下,怎么計算新的狀態呢?
我們設 raf callback 的延遲時間為 Δt ,第二部分已經說過,這個 Δt 是瀏覽器自己決定的。
不管 Δt 是多少,可以用幾個緩動過程連續疊加(一個緩動過程的時間是 dt )來拼湊出 Δt 。
不過 Δt 往往不是 dt 的整數倍,對于最后多出來的一小塊時間,我們可以取一個比例值。
const dt = 1000 / 60;
let preTime = 0
, initialState = {
currX: -250,
currV: 0,
}
const update = () => {
const currTime = performance.now();
const deltaTime = currTime - preTime;
const steps = deltaTime / dt;
const multiObj = (obj, k) => {
return Object.keys(obj).reduce((res, key) => {
return { ...res, [key]: obj[key] * k }
}, {})
};
const getCurrState = (prevState, steps) => {
if (steps < 1) {
return multiObj(cal(prevState), steps)
}
return getCurrState(cal(prevState), steps - 1)
};
render(getCurrState(initialState, steps))
raf(update);
}
update()
動畫漫談
CSS 動畫與 JS 動畫的區別是,使用 CSS 動畫,不需要寫緩動過程。比如在 transition 中,可以使用現成的 cubic bizier 的緩動(其中 ease, ease-in, ease-out 等都是特定參數值的 cubic bizier)。
(值得一提的是,transition的實現也使用了 raf 的機制,當標簽頁被切換時, transition 動畫也會暫停,大家不妨試一試)
CSS 的 animation 使用設置關鍵幀的方式實現動畫,適合完成多步、往返或者不斷重復的動畫。
那么我們什么時候需要 JS 動畫呢——當你對 CSS 提供的緩動函數不滿意的時候。打個比方,如果想實現像淘寶網在加購成功后,讓商品 logo 沿著弧線運動的動畫。
React Motion 所做的事,只不過自己實現了一套緩動函數。如果你不關心緩動過程,用 CSS 動畫可以直接替換。
至于 React 當中的 ReactCSSTransitionGroup,是React提供的支持列表動畫的 API 。試想一下,當渲染函數發現新的列表狀態中,消失了某一項。那么要繪制這一項消失的動畫,必須先讓這一項暫存在 DOM 中,直到動畫結束,再從 DOM 消失。這個實現起來比較麻煩,所以 React 提供了這個 API 幫助我們實現動畫。值得注意的是,ReactCSSTransitionGroup 只是對列表的增與刪提供動畫支持。如果只是對列表項進行修改,不要生硬的套用 ReactCSSTransitionGroup,自己在 state 中管理列表實現起來更加方便。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/78388.html