成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

React Motion 緩動(dòng)函數(shù)剖析

wfc_666 / 2832人閱讀

摘要:大家可以嘗試使用的,配置一個(gè)合適的勁度系數(shù)和空氣阻力。所做的事,只不過(guò)自己實(shí)現(xiàn)了一套緩動(dòng)函數(shù)。

根據(jù)經(jīng)典力學(xué)的觀點(diǎn),世界上所有的原子每時(shí)每刻仿佛都會(huì)根據(jù)當(dāng)前速度、受力和位置計(jì)算出下一刻的速度、受力和位置。上帝有一臺(tái)超級(jí)計(jì)算機(jī)嗎?非也,反而計(jì)算機(jī)是我們利用原子的這些特性拼裝出來(lái)的。現(xiàn)在,我們卻要用計(jì)算機(jī),像上帝那樣,再造一個(gè)世界。

我不知道這個(gè)世界上有沒(méi)有“仿世學(xué)”,但是既然動(dòng)畫是要模仿現(xiàn)實(shí)世界,那么實(shí)現(xiàn)動(dòng)畫的根本方法就是借鑒上帝的辦法——模擬自然規(guī)律。本文以 React Motion 實(shí)現(xiàn)原理為背景,介紹一種通用的模擬物理規(guī)律的方法,以及如何使用這種方法實(shí)現(xiàn) React Motion 的緩動(dòng)函數(shù)。讓我們來(lái)當(dāng)一回上帝吧。

什么是緩動(dòng)函數(shù)

動(dòng)畫的原理看似復(fù)雜,其實(shí)就是每幀不停得渲染。一張靜態(tài)頁(yè)面的渲染就是在一幀中渲染。如何渲染每一幀呢?我們可以用最簡(jiǎn)單,同時(shí)也是最繁瑣的方法,就像最原始的動(dòng)畫片那樣,寫 n 張靜態(tài)頁(yè)面,然后每隔一幀切換一張。

假如我們已經(jīng)勤奮地寫好了 P_1, P_2, ... P_n 這 n 張頁(yè)面,我們用它來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的動(dòng)畫:

// pages: [P1, P2, P3 ... Pn];
const pageCount = pages.length;

const startAnimation = (currPageIndex) => {
  if (currIndex === pageCount) { return ; }
 
  document.body.innerHTML = (pages[currPageIndex++]);

  setTimeout(startAnimation.bind(null, currPageIndex), frameTime)
}

startAnimation(0);

用這種方法有著顯而易見(jiàn)的問(wèn)題:

寫 n 張頁(yè)面頁(yè)面渲染效率十分低下。

每次重新設(shè)置 body.innerHTML,性能太低了。

我們來(lái)逐個(gè)解決上述問(wèn)題。

每一幀的界面都遵循一定的規(guī)律,相似性很高,中間必然有很多重復(fù)勞動(dòng)。既然是重復(fù)勞動(dòng),我們可以放心的交給計(jì)算機(jī)去完成。寫一個(gè)渲染函數(shù),只需要向這個(gè)函數(shù)描述一下當(dāng)前頁(yè)面的信息,這個(gè)函數(shù)就能把頁(yè)面給渲染出來(lái)。

可以用局部更新的方式來(lái)取代塊更新,其中 React 的 Virtual DOM 更新方便地解決了這個(gè)問(wèn)題。

我們?cè)僖砸粋€(gè)左右切換的 toggle 動(dòng)畫為例,寫一個(gè)渲染函數(shù):

const render = x => `
  
`

有了這個(gè)函數(shù)之后,只需要告訴它 x 的當(dāng)前值,新的頁(yè)面就開(kāi)始自動(dòng)繪制了。由于 toggle 的運(yùn)動(dòng)規(guī)律,x 的值也不用手動(dòng)依次給出,我們?nèi)匀豢梢詫懸粋€(gè)自動(dòng)計(jì)算 x 的函數(shù)。這個(gè)自動(dòng)計(jì)算 x 的函數(shù),或者說(shuō)計(jì)算頁(yè)面狀態(tài)的函數(shù),就是緩動(dòng)函數(shù)。

假設(shè)這個(gè) toggle 是勻速運(yùn)動(dòng)的,緩動(dòng)函數(shù)便可以寫成這樣:

$$ distance(總路程) = endX - beginX $$

$$ v = frac{distance}{duration(總時(shí)間)} $$

$$ x = v cdot t + beginX $$

用代碼來(lái)表示,

const cal = (beginX, endX, duration, beginTime) => {
    const now = performance.now();
    const passedTime = now - beginTime;

    return (endX - beginX) / duration * passedTime + beginX;
}

最后完成這個(gè) toggle 動(dòng)畫:

const beginX = 0;
const endX = 300;
const duration = 5000;
const frameTime = 1000 / 60;
    
let beginTime = performance.now();

const startAnimation = () => {
  const currX = cal(beginX, endX, duration, beginTime);

  document.body.innerHTML = render(currX);

  setTimeout(startAnimation, frameTime);
}

startAnimation(0);
requestAnimationFrame (raf)

可以看到,上述章節(jié)使用 setTimeout 來(lái)模擬時(shí)間的逝去,然而瀏覽器為動(dòng)畫過(guò)程提供了一個(gè)更為專注的 API - requestAnimationFrame。

const update = now => {
  // calculate new state...

  // rerender here...

  raf(update);
};

raf(update);

raf 使用起來(lái)就像 setTimeout 一樣,但有以下優(yōu)點(diǎn):

所有注冊(cè)到 raf 中的回調(diào),瀏覽器會(huì)統(tǒng)一管理, 在適當(dāng)?shù)臅r(shí)候一同執(zhí)行所有回調(diào)。

當(dāng)頁(yè)面不可見(jiàn),例如當(dāng)前標(biāo)簽頁(yè)被切換,隱藏在后面的時(shí)候,為了減少終端的損耗,raf 就會(huì)暫停。(如果像 jQuery 那樣, 使用 setTimeout 實(shí)現(xiàn)動(dòng)畫,此時(shí)頁(yè)面就會(huì)進(jìn)行沒(méi)有意義的重繪)。

raf 的這個(gè)特性,還可以利用在實(shí)時(shí)模塊中,讓標(biāo)簽頁(yè)隱藏時(shí)停止發(fā)請(qǐng)求。

在開(kāi)始使用 raf 前,我們需要一個(gè) raf 的 polyfill ,比如 chrisdickinson/raf

然后,我們嘗試用 React 和 raf 來(lái)重構(gòu)一次 Toggle 動(dòng)畫。在數(shù)據(jù)上,用中介者模式實(shí)現(xiàn)一個(gè)簡(jiǎn)單的單向數(shù)據(jù)流:

const createStoreX = initialX => {
  let currX = initialX;
  let listeners = [];

  return {
    getX: () => currX,

    subscribe: listener => {
      listeners = [...listeners, listener];
    },

    changeX: newX => {
      currX = newX;
      listeners.forEach(listener);
    }
  };
}

const finalCreateStoreX = (createStoreX => initialX => {
  const store = createStoreX(initialX);

  return {
    ...store,
    changeX: newX => {
      store.changeX(newX);
    }
  };
})(createStoreX);

const store = finalCreateStoreX(0);

const View = x => (
  
); 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)

有沒(méi)有覺(jué)得很棒!但仍然有優(yōu)化的空間。動(dòng)畫是源自現(xiàn)實(shí)世界的,人類早已習(xí)慣了一個(gè)變速運(yùn)動(dòng)的物理環(huán)境,這樣的一個(gè)勻速動(dòng)畫會(huì)讓人相對(duì)感覺(jué)不適。為了優(yōu)化用戶體驗(yàn),React Motion 使用了一種常見(jiàn)的變速運(yùn)動(dòng) —— 彈簧運(yùn)動(dòng)。

React Motion 緩動(dòng)原理剖析

React Motion 使動(dòng)畫看起來(lái)像一個(gè)彈簧那樣(一個(gè)有空氣阻力的彈簧,如果沒(méi)有空氣阻力,彈簧就會(huì)不停地做簡(jiǎn)諧運(yùn)動(dòng)了)。大家可以嘗試使用 React Motion 的spring-parameters-chooser,配置一個(gè)合適的勁度系數(shù)和空氣阻力。彈簧動(dòng)畫可以使網(wǎng)站增添一些俏皮的元素,讓用戶體驗(yàn)起來(lái)更加舒暢!

下面就讓我們進(jìn)入主題,開(kāi)始解讀 React Motion 的緩動(dòng)過(guò)程。

先模擬彈簧的物理規(guī)律,實(shí)現(xiàn)彈簧動(dòng)畫。

假設(shè)有一個(gè)彈簧,彈簧上綁了一個(gè)砝碼,回到初中物理,根據(jù)胡克定律,砝碼的受到彈簧的拉力為:

$$ F_{spring} = kvarDelta{x} (k為彈簧的勁度系數(shù))$$

我們假設(shè)該砝碼受到的空氣阻力 kdamping 與砝碼當(dāng)前的速度 vt 呈正相關(guān),其中阻尼系數(shù)為 kdamping

對(duì)砝碼進(jìn)行受力分析得:

$$ F = F_{spring} - F_{damping} = k_{spring}varDelta{x} - k_{damping} imes v_{t} $$

設(shè) at 為砝碼當(dāng)前加速度,得:

$$ F = ma_t $$

設(shè) v" 和 x" 分別為經(jīng)過(guò) $$ dt $$ 時(shí)間后,砝碼新的速度和位移,得:

$$ 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 $$

我們拿到了計(jì)算新?tīng)顟B(tài)的公式,但是 dt 是無(wú)限趨近于 0,怎么去模擬這個(gè)無(wú)限趨近于 0 呢?

現(xiàn)在只知道,當(dāng) dt 越趨近于 0 時(shí),等式兩邊的值越接近(極限的單調(diào)有界準(zhǔn)則可證)??梢园?dt 設(shè)為一個(gè)非常小的常量,雖然會(huì)造成一定的誤差,但是不足為慮,只要騙過(guò)人類的眼睛就可以了。

這樣我們就可以計(jì)算得出 v" 和 x" 。對(duì)以上過(guò)程不斷重復(fù),就能計(jì)算出任意時(shí)刻的位移和速度。

這是個(gè)通用的模擬物理規(guī)律的緩動(dòng)過(guò)程,是否讓你茅塞頓開(kāi)?看一個(gè)同樣的模擬物理規(guī)律的動(dòng)畫,有沒(méi)有手癢?

但是,原諒我又說(shuō)了 “但是”,如果我們要用 raf 實(shí)現(xiàn)這個(gè)緩動(dòng)的話,raf 不能設(shè)置 callback 的延遲時(shí)間,而我們的 dt 是一個(gè)固定的非常小的常量。這種情況下,怎么計(jì)算新的狀態(tài)呢?

我們?cè)O(shè) raf callback 的延遲時(shí)間為 Δt ,第二部分已經(jīng)說(shuō)過(guò),這個(gè) Δt 是瀏覽器自己決定的。

不管 Δt 是多少,可以用幾個(gè)緩動(dòng)過(guò)程連續(xù)疊加(一個(gè)緩動(dòng)過(guò)程的時(shí)間是 dt )來(lái)拼湊出 Δt 。

不過(guò) Δt 往往不是 dt 的整數(shù)倍,對(duì)于最后多出來(lái)的一小塊時(shí)間,我們可以取一個(gè)比例值。

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()
動(dòng)畫漫談

CSS 動(dòng)畫與 JS 動(dòng)畫的區(qū)別是,使用 CSS 動(dòng)畫,不需要寫緩動(dòng)過(guò)程。比如在 transition 中,可以使用現(xiàn)成的 cubic bizier 的緩動(dòng)(其中 ease, ease-in, ease-out 等都是特定參數(shù)值的 cubic bizier)。

(值得一提的是,transition的實(shí)現(xiàn)也使用了 raf 的機(jī)制,當(dāng)標(biāo)簽頁(yè)被切換時(shí), transition 動(dòng)畫也會(huì)暫停,大家不妨試一試)

CSS 的 animation 使用設(shè)置關(guān)鍵幀的方式實(shí)現(xiàn)動(dòng)畫,適合完成多步、往返或者不斷重復(fù)的動(dòng)畫。

那么我們什么時(shí)候需要 JS 動(dòng)畫呢——當(dāng)你對(duì) CSS 提供的緩動(dòng)函數(shù)不滿意的時(shí)候。打個(gè)比方,如果想實(shí)現(xiàn)像淘寶網(wǎng)在加購(gòu)成功后,讓商品 logo 沿著弧線運(yùn)動(dòng)的動(dòng)畫。

React Motion 所做的事,只不過(guò)自己實(shí)現(xiàn)了一套緩動(dòng)函數(shù)。如果你不關(guān)心緩動(dòng)過(guò)程,用 CSS 動(dòng)畫可以直接替換。

至于 React 當(dāng)中的 ReactCSSTransitionGroup,是React提供的支持列表動(dòng)畫的 API 。試想一下,當(dāng)渲染函數(shù)發(fā)現(xiàn)新的列表狀態(tài)中,消失了某一項(xiàng)。那么要繪制這一項(xiàng)消失的動(dòng)畫,必須先讓這一項(xiàng)暫存在 DOM 中,直到動(dòng)畫結(jié)束,再?gòu)?DOM 消失。這個(gè)實(shí)現(xiàn)起來(lái)比較麻煩,所以 React 提供了這個(gè) API 幫助我們實(shí)現(xiàn)動(dòng)畫。值得注意的是,ReactCSSTransitionGroup 只是對(duì)列表的增與刪提供動(dòng)畫支持。如果只是對(duì)列表項(xiàng)進(jìn)行修改,不要生硬的套用 ReactCSSTransitionGroup,自己在 state 中管理列表實(shí)現(xiàn)起來(lái)更加方便。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/78388.html

相關(guān)文章

  • React Web 動(dòng)畫的 5 種創(chuàng)建方式,每一種都不簡(jiǎn)單

    摘要:以前一直投入在中,寫動(dòng)畫的時(shí)候不是用中的,就是依賴像這樣的庫(kù),最近轉(zhuǎn)向,在得到很多大佬關(guān)于動(dòng)畫的回應(yīng),于是決定分享給大家,如有其他見(jiàn)解,非常歡迎在下面評(píng)論中交流以下便是本文要分享的創(chuàng)建動(dòng)畫的幾種方式下面,勒次個(gè)特斯大特一特給元素添加是最簡(jiǎn)單 以前一直投入在 React Native 中,寫動(dòng)畫的時(shí)候不是用 CSS 中的 transitions / animations,就是依賴像 Gr...

    solocoder 評(píng)論0 收藏0
  • react進(jìn)階漫談

    摘要:父組件向子組件之間非常常見(jiàn),通過(guò)機(jī)制傳遞即可。我們應(yīng)該聽(tīng)說(shuō)過(guò)高階函數(shù),這種函數(shù)接受函數(shù)作為輸入,或者是輸出一個(gè)函數(shù),比如以及等函數(shù)。在傳遞數(shù)據(jù)的時(shí)候,我們可以用進(jìn)一步提高性能。 本文主要談自己在react學(xué)習(xí)的過(guò)程中總結(jié)出來(lái)的一些經(jīng)驗(yàn)和資源,內(nèi)容邏輯參考了深入react技術(shù)棧一書以及網(wǎng)上的諸多資源,但也并非完全照抄,代碼基本都是自己實(shí)踐,主要為平時(shí)個(gè)人學(xué)習(xí)做一個(gè)總結(jié)和參考。 本文的關(guān)鍵...

    neuSnail 評(píng)論0 收藏0
  • react進(jìn)階漫談

    摘要:父組件向子組件之間非常常見(jiàn),通過(guò)機(jī)制傳遞即可。我們應(yīng)該聽(tīng)說(shuō)過(guò)高階函數(shù),這種函數(shù)接受函數(shù)作為輸入,或者是輸出一個(gè)函數(shù),比如以及等函數(shù)。在傳遞數(shù)據(jù)的時(shí)候,我們可以用進(jìn)一步提高性能。 本文主要談自己在react學(xué)習(xí)的過(guò)程中總結(jié)出來(lái)的一些經(jīng)驗(yàn)和資源,內(nèi)容邏輯參考了深入react技術(shù)棧一書以及網(wǎng)上的諸多資源,但也并非完全照抄,代碼基本都是自己實(shí)踐,主要為平時(shí)個(gè)人學(xué)習(xí)做一個(gè)總結(jié)和參考。 本文的關(guān)鍵...

    wyk1184 評(píng)論0 收藏0
  • react進(jìn)階漫談

    摘要:父組件向子組件之間非常常見(jiàn),通過(guò)機(jī)制傳遞即可。我們應(yīng)該聽(tīng)說(shuō)過(guò)高階函數(shù),這種函數(shù)接受函數(shù)作為輸入,或者是輸出一個(gè)函數(shù),比如以及等函數(shù)。在傳遞數(shù)據(jù)的時(shí)候,我們可以用進(jìn)一步提高性能。 本文主要談自己在react學(xué)習(xí)的過(guò)程中總結(jié)出來(lái)的一些經(jīng)驗(yàn)和資源,內(nèi)容邏輯參考了深入react技術(shù)棧一書以及網(wǎng)上的諸多資源,但也并非完全照抄,代碼基本都是自己實(shí)踐,主要為平時(shí)個(gè)人學(xué)習(xí)做一個(gè)總結(jié)和參考。 本文的關(guān)鍵...

    junnplus 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<