React中常見(jiàn)的動(dòng)畫實(shí)現(xiàn)的幾種方式
現(xiàn)在,用戶對(duì)于前端頁(yè)面的要求已經(jīng)不能滿足于實(shí)現(xiàn)功能,更要有顏值,有趣味。除了整體 UI 的美觀,在合適的地方添加合適的動(dòng)畫效果往往比靜態(tài)頁(yè)面更具有表現(xiàn)力,達(dá)到更自然的效果。比如,一個(gè)簡(jiǎn)單的 loading 動(dòng)畫或者頁(yè)面切換效果不僅能緩解用戶的等待情緒,甚至通過(guò)使用品牌 logo 等形式,默默達(dá)到品牌宣傳的效果。
React 作為最近幾年比較流行的前端開(kāi)發(fā)框架,提出了虛擬 DOM 概念,所有 DOM 的變化都先發(fā)生在虛擬 DOM 上,通過(guò) DOM diff 來(lái)分析網(wǎng)頁(yè)的實(shí)際變化,然后反映在真實(shí) DOM 上,從而極大地提升網(wǎng)頁(yè)性能。然而,在動(dòng)畫實(shí)現(xiàn)方面,React 作為框架并不會(huì)直接給組件提供動(dòng)畫效果,需要開(kāi)發(fā)者自行實(shí)現(xiàn),而傳統(tǒng) web 動(dòng)畫大多數(shù)都通過(guò)直接操作實(shí)際 DOM 元素來(lái)實(shí)現(xiàn),這在 React 中顯然是不被提倡的。那么,在 React 中動(dòng)畫都是如何實(shí)現(xiàn)的呢?
所有動(dòng)畫的本質(zhì)都是連續(xù)修改 DOM 元素的一個(gè)或者多個(gè)屬性,使其產(chǎn)生連貫的變化效果,從而形成動(dòng)畫。在 React 中實(shí)現(xiàn)動(dòng)畫本質(zhì)上與傳統(tǒng) web 動(dòng)畫一樣,仍然是兩種方式: 通過(guò) css3 動(dòng)畫實(shí)現(xiàn)和通過(guò) js 修改元素屬性。只不過(guò)在具體實(shí)現(xiàn)時(shí),要更為符合 React 的框架特性,可以概括為幾類:
- 基于定時(shí)器或 requestAnimationFrame(RAF) 的間隔動(dòng)畫;
- 基于 css3 的簡(jiǎn)單動(dòng)畫;
- React 動(dòng)畫插件 CssTransitionGroup;
- 結(jié)合 hook 實(shí)現(xiàn)復(fù)雜動(dòng)畫;
- 其他第三方動(dòng)畫庫(kù)。
一、基于定時(shí)器或 RAF 的間隔動(dòng)畫
最早,動(dòng)畫的實(shí)現(xiàn)都是依靠定時(shí)器 setInterval , setTimeout 或者 requestAnimationFrame (RAF) 直接修改 DOM 元素的屬性。不熟悉 React 特性的開(kāi)發(fā)者可能會(huì)習(xí)慣性地通過(guò) ref 或者 findDOMNode() 獲取真實(shí)的 DOM 節(jié)點(diǎn),直接修改其樣式。然而,通過(guò) ref 直接獲取真實(shí) DOM 并對(duì)其操作是是不被提倡使用,應(yīng)當(dāng)盡量避免這種操作。
因此,我們需要將定時(shí)器或者 RAF 等方法與 DOM 節(jié)點(diǎn)屬性通過(guò) state 聯(lián)系起來(lái)。首先,需要提取出與變化樣式相關(guān)的屬性,替換為 state ,然后在合適的生命周期函數(shù)中添加定時(shí)器或者 requestAnimationFrame 不斷修改 state ,觸發(fā)組件更新,從而實(shí)現(xiàn)動(dòng)畫效果。
示例
以一個(gè)進(jìn)度條為例,代碼如下所示:
// 使用requestAnimationFrame改變state
import React, { Component } from 'react';
export default class Progress extends Component {
constructor(props) {
super(props);
this.state = {
percent: 10
};
}
increase = () => {
const percent = this.state.percent;
const targetPercent = percent >= 90 ? 100 : percent + 10;
const speed = (targetPercent - percent) / 400;
let start = null;
const animate = timestamp => {
if (!start) start = timestamp;
const progress = timestamp - start;
const currentProgress = Math.min(parseInt(speed * progress + percent, 10), targetPercent);
this.setState({
percent: currentProgress
});
if (currentProgress < targetPercent) {
window.requestAnimationFrame(animate);
}
};
window.requestAnimationFrame(animate);
}
decrease = () => {
const percent = this.state.percent;
const targetPercent = percent < 10 ? 0 : percent - 10;
const speed = (percent - targetPercent) / 400;
let start = null;
const animate = timestamp => {
if (!start) start = timestamp;
const progress = timestamp - start;
const currentProgress = Math.max(parseInt(percent - speed * progress, 10), targetPercent);
this.setState({
percent: currentProgress
});
if (currentProgress > targetPercent) {
window.requestAnimationFrame(animate);
}
};
window.requestAnimationFrame(animate);
}
render() {
const { percent } = this.state;
return (
<div>
<div className="progress">
<div className="progress-wrapper" >
<div className="progress-inner" style = {{width: `${percent}%`}} ></div>
</div>
<div className="progress-info" >{percent}%</div>
</div>
<div className="btns">
<button onClick={this.decrease}>-</button>
<button onClick={this.increase}>+</button>
</div>
</div>
);
}
}
在示例中,我們?cè)?increase 和 decrease 函數(shù)中構(gòu)建線性過(guò)渡函數(shù) animation , requestAnimationFrame 在瀏覽器每次重繪前執(zhí)行會(huì)執(zhí)行過(guò)渡函數(shù),計(jì)算當(dāng)前進(jìn)度條 width 屬性并更新該 state ,使得進(jìn)度條重新渲染。該示例的效果如下所示:

這種實(shí)現(xiàn)方式在使用 requestAnimationFrame 時(shí)性能不錯(cuò),完全使用純 js 實(shí)現(xiàn),不依賴于 css,使用定時(shí)器時(shí)可能出現(xiàn)掉幀卡頓現(xiàn)象。此外,還需要開(kāi)發(fā)者根據(jù)速度函數(shù)自己計(jì)算狀態(tài),比較復(fù)雜。
二、基于 css3 的簡(jiǎn)單動(dòng)畫
當(dāng) css3 中的 animation 和 transition 出現(xiàn)和普及后,我們可以輕松地利用 css 實(shí)現(xiàn)元素樣式的變化,而不用通過(guò)人為計(jì)算實(shí)時(shí)樣式。
示例
我們?nèi)砸陨厦娴倪M(jìn)度條為例,使用 css3 實(shí)現(xiàn)進(jìn)度條動(dòng)態(tài)效果,代碼如下所示:
import React, { Component } from 'react';
export default class Progress extends Component {
constructor(props) {
super(props);
this.state = {
percent: 10
};
}
increase = () => {
const percent = this.state.percent + 10;
this.setState({
percent: percent > 100 ? 100 : percent,
})
}
decrease = () => {
const percent = this.state.percent - 10;
this.setState({
percent: percent < 0 ? 0 : percent,
})
}
render() {
// 同上例, 省略
....
}
}
.progress-inner {
transition: width 400ms cubic-bezier(0.08, 0.82, 0.17, 1);
// 其他樣式同上,省略
...
}
在示例中, increase 和 decrease 函數(shù)中不再計(jì)算 width ,而是直接設(shè)置增減后的寬度。需要注意的是,在 css 樣式中設(shè)置了 transition 屬性,該屬性在其指定的 transition-property 發(fā)生變化時(shí)自動(dòng)實(shí)現(xiàn)樣式的動(dòng)態(tài)變化效果,并且可以設(shè)置不同的速度效果的速度曲線。該示例的效果如下圖所示,可以發(fā)現(xiàn),與上一個(gè)例子不同的是,右側(cè)的進(jìn)度數(shù)據(jù)是直接變化為目標(biāo)數(shù)字,沒(méi)有具體的變化過(guò)程,而進(jìn)度條的動(dòng)態(tài)效果因?yàn)椴辉偈蔷€性變化,效果更為生動(dòng)。

基于 css3 的實(shí)現(xiàn)方式具有較高的性能,代碼量少,但是只能依賴于 css 效果,對(duì)于復(fù)雜動(dòng)畫也很難實(shí)現(xiàn)。此外,通過(guò)修改 state 實(shí)現(xiàn)動(dòng)畫效果,只能作用于已經(jīng)存在于 DOM 樹(shù)中的節(jié)點(diǎn)。如果想用這種方式為組件添加入場(chǎng)和離場(chǎng)動(dòng)畫,需要維持至少兩個(gè) state 來(lái)實(shí)現(xiàn)入場(chǎng)和離場(chǎng)動(dòng)畫,其中一個(gè) state 用于控制元素是否顯示,另一個(gè) state 用于控制元素在動(dòng)畫中的變化屬性。在這種情況下,開(kāi)發(fā)者需要花費(fèi)大量精力來(lái)維護(hù)組件的動(dòng)畫邏輯,十分復(fù)雜繁瑣。
三、React 動(dòng)畫插件 CssTransitionGroup
React 曾為開(kāi)發(fā)者提供過(guò)動(dòng)畫插件 react-addons-css-transition-group ,后交由社區(qū)維護(hù),形成現(xiàn)在的 react-transition-group ,該插件可以方便地實(shí)現(xiàn)組件的入場(chǎng)和離場(chǎng)動(dòng)畫,使用時(shí)需要開(kāi)發(fā)者額外安裝。 react-transition-group 包含 CSSTransitionGroup 和 TransitionGroup 兩個(gè)動(dòng)畫插件,其中,后者是底層 api,前者是后者的進(jìn)一步封裝,可以較為便捷地實(shí)現(xiàn) css 動(dòng)畫。
示例
以一個(gè)動(dòng)態(tài)增加tab的為例,代碼如下:
import React, { Component } from 'react';
import { CSSTransitionGroup } from 'react-transition-group';
let uid = 2;
export default class Tabs extends Component {
constructor(props) {
super(props);
this.state = {
activeId: 1,
tabData: [{
id: 1,
panel: '選項(xiàng)1'
}, {
id: 2,
panel: '選項(xiàng)2'
}]
};
}
addTab = () => {
// 添加tab代碼
...
}
deleteTab = (id) => {
// 刪除tab代碼
...
}
render() {
const { tabData, activeId } = this.state;
const renderTabs = () => {
return tabData.map((item, index) => {
return (
<div
className={`tab-item${item.id === activeId ? ' tab-item-active' : ''}`}
key={`tab${item.id}`}
>
{item.panel}
<span className="btns btn-delete" onClick={() => this.deleteTab(item.id)}>✕</span>
</div>
);
})
}
return (
<div>
<div className="tabs" >
<CSSTransitionGroup
transitionName="tabs-wrap"
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
>
{renderTabs()}
</CSSTransitionGroup>
<span className="btns btn-add" onClick={this.addTab}>+</span>
</div>
<div className="tab-cont">
cont
</div>
</div>
);
}
}
/* tab動(dòng)態(tài)增加動(dòng)畫 */
.tabs-wrap-enter {
opacity: 0.01;
}
.tabs-wrap-enter.tabs-wrap-enter-active {
opacity: 1;
transition: all 500ms ease-in;
}
.tabs-wrap-leave {
opacity: 1;
}
.tabs-wrap-leave.tabs-wrap-leave-active {
opacity: 0.01;
transition: all 500ms ease-in;
}
CSSTransitionGroup 可以為其子節(jié)點(diǎn)添加額外的 css 類,然后通過(guò) css 動(dòng)畫達(dá)到入場(chǎng)和離場(chǎng)動(dòng)畫效果。為了給每個(gè) tab 節(jié)點(diǎn)添加動(dòng)畫效果,需要先將它們包裹在 CSSTransitionGroup 組件中。 當(dāng)設(shè)定 transitionName 屬性為 'tabs-wrapper' , transitionEnterTimeout 為400毫秒后,一旦 CSSTransitionGroup 中新增節(jié)點(diǎn),該新增節(jié)點(diǎn)會(huì)在出現(xiàn)時(shí)被添加上 css 類 'tabs-wrapper-enter' ,然后在下一幀時(shí)被添加上 css 類 'tabs-wrapper-enter-active' 。由于這兩個(gè) css 類中設(shè)定了不同的透明度和 css3 transition 屬性,所以節(jié)點(diǎn)實(shí)現(xiàn)了透明度由小到大的入場(chǎng)效果。400毫秒后 css 類 'tabs-wrapper-enter' 和 'tabs-wrapper-enter-active' 將會(huì)同時(shí)被移除,節(jié)點(diǎn)完成整個(gè)入場(chǎng)動(dòng)畫過(guò)程。離場(chǎng)動(dòng)畫的實(shí)現(xiàn)類似于入場(chǎng)動(dòng)畫,只不過(guò)被添加的 css 類名為 'tabs-wrapper-leave' 和 'tabs-wrapper-leave-active' 。該示例效果如下圖所示:

CSSTransitionGroup 支持以下7個(gè)屬性:

其中,入場(chǎng)和離場(chǎng)動(dòng)畫是默認(rèn)開(kāi)啟的,使用時(shí)需要設(shè)置 transitionEnterTimeout 和 transitionLeaveTimeout 。值得注意的是, CSSTransitionGroup 還提供出現(xiàn)動(dòng)畫(appear),使用時(shí)需要設(shè)置 transitionAppearTimeout 。那么,出現(xiàn)動(dòng)畫和入場(chǎng)動(dòng)畫有什么區(qū)別呢?當(dāng)設(shè)定 transitionAppear 為 true 時(shí), CSSTransitionGroup 在 初次渲染 時(shí),會(huì)添加一個(gè)出現(xiàn)階段。在該階段中, CSSTransitionGroup 的已有子節(jié)點(diǎn)都會(huì)被相繼添加 css 類 'tabs-wrapper-appear' 和 'tabs-wrapper-appear-active' ,實(shí)現(xiàn)出現(xiàn)動(dòng)畫效果。因此, 出現(xiàn)動(dòng)畫僅適用于 CSSTransitionGroup 在初次渲染時(shí)就存在的子節(jié)點(diǎn) ,一旦 CSSTransitionGroup 完成渲染,其子節(jié)點(diǎn)就只可能有入場(chǎng)動(dòng)畫(enter),不可能有出現(xiàn)動(dòng)畫(appear)。
此外,使用 CSSTransitionGroup 需要注意以下幾點(diǎn):
CSSTransitionGroup默認(rèn)在 DOM 樹(shù)中生成一個(gè)span標(biāo)簽包裹其子節(jié)點(diǎn),如果想要使用其他 html 標(biāo)簽,可設(shè)定CSSTransitionGroup的component屬性;CSSTransitionGroup的子元素必須添加key值才會(huì)在節(jié)點(diǎn)發(fā)生變化時(shí),準(zhǔn)確地計(jì)算出哪些節(jié)點(diǎn)需要添加入場(chǎng)動(dòng)畫,哪些節(jié)點(diǎn)需要添加離場(chǎng)動(dòng)畫;CSSTransitionGroup的動(dòng)畫效果只作用于直接子節(jié)點(diǎn),不作用于其孫子節(jié)點(diǎn);- 動(dòng)畫的結(jié)束時(shí)間不以 css 中 transition-duration 為準(zhǔn),而是以
transitionEnterTimeout,transitionLeaveTimeout,TransitionAppearTimeout為準(zhǔn),因?yàn)槟承┣闆r下 transitionend 事件不會(huì)被觸發(fā),詳見(jiàn) MDN transitionend 。
CSSTransitionGroup 實(shí)現(xiàn)動(dòng)畫的優(yōu)點(diǎn)是:
- 簡(jiǎn)單易用,可以方便快捷地實(shí)現(xiàn)元素的入場(chǎng)和離場(chǎng)動(dòng)畫;
- 與 React 結(jié)合,性能比較好。
CSSTransitionGroup 缺點(diǎn)也十分明顯:
- 局限于出現(xiàn)動(dòng)畫,入場(chǎng)動(dòng)畫和離場(chǎng)動(dòng)畫;
- 由于需要制定
transitionName,靈活性不夠; - 只能依靠 css 實(shí)現(xiàn)簡(jiǎn)單的動(dòng)畫。
四、結(jié)合 hook 實(shí)現(xiàn)復(fù)雜動(dòng)畫
在實(shí)際項(xiàng)目中,可能需要一些更炫酷的動(dòng)畫效果,這些效果僅依賴于 css3 往往較難實(shí)現(xiàn)。此時(shí),我們不妨借助一些成熟的第三方庫(kù),如 jQuery 或 GASP,結(jié)合 React 組件中的生命周期鉤子方法 hook 函數(shù),實(shí)現(xiàn)復(fù)雜動(dòng)畫效果。除了 React 組件正常的生命周期外, CSSTransitionGroup 的底層 api TransitonGroup 還為其子元素額外提供了一系列特殊的生命周期 hook 函數(shù),在這些 hook 函數(shù)中結(jié)合第三方動(dòng)畫庫(kù)可以實(shí)現(xiàn)豐富的入場(chǎng)、離場(chǎng)動(dòng)畫效果。
TransisitonGroup 分別提供一下六個(gè)生命周期 hook 函數(shù):
- componentWillAppear(callback)
- componentDidAppear()
- componentWillEnter(callback)
- componentDidEnter()
- componentWillLeave(callback)
- componentDidLeave()
它們的觸發(fā)時(shí)機(jī)如圖所示:

示例
GASP 是一個(gè) flash 時(shí)代發(fā)展至今的動(dòng)畫庫(kù),借鑒視頻幀的概念,特別適合做長(zhǎng)時(shí)間的序列動(dòng)畫效果。本文中,我們用 TransitonGroup 和 react-gsap-enhancer (一個(gè)可以將 GSAP 應(yīng)用于 React 的增強(qiáng)庫(kù))完成一個(gè)圖片畫廊,代碼如下:
import React, { Component } from 'react';
import { TransitionGroup } from 'react-transition-group';
import GSAP from 'react-gsap-enhancer'
import { TimelineMax, Back, Sine } from 'gsap';
class Photo extends Component {
constructor(props) {
super(props);
}
componentWillEnter(callback) {
this.addAnimation(this.enterAnim, {callback: callback})
}
componentWillLeave(callback) {
this.addAnimation(this.leaveAnim, {callback: callback})
}
enterAnim = (utils) => {
const { id } = this.props;
return new TimelineMax()
.from(utils.target, 1, {
x: `+=${( 4 - id ) * 60}px`,
autoAlpha: 0,
onComplete: utils.options.callback,
}, id * 0.7);
}
leaveAnim = (utils) => {
const { id } = this.props;
return new TimelineMax()
.to(utils.target, 0.5, {
scale: 0,
ease: Sine.easeOut,
onComplete: utils.options.callback,
}, (4 - id) * 0.7);
}
render() {
const { url } = this.props;
return (
<div className="photo">
<img src={url} />
</div>
)
}
}
const WrappedPhoto = GSAP()(Photo);
export default class Gallery extends Component {
constructor(props) {
super(props);
this.state = {
show: false,
photos: [{
id: 1,
url: 'http://img4.imgtn.bdimg.com/it/u=1032683424,3204785822&fm=214&gp=0.jpg'
}, {
id: 2,
url: 'http://imgtu.5011.net/uploads/content/20170323/7488001490262119.jpg'
}, {
id: 3,
url: 'http://tupian.enterdesk.com/2014/lxy/2014/12/03/18/10.jpg'
}, {
id: 4,
url: 'http://img4.imgtn.bdimg.com/it/u=360498760,1598118672&fm=27&gp=0.jpg'
}]
};
}
toggle = () => {
this.setState({
show: !this.state.show
})
}
render() {
const { show, photos } = this.state;
const renderPhotos = () => {
return photos.map((item, index) => {
return <WrappedPhoto id={item.id} url={item.url} key={`photo${item.id}`} />;
})
}
return (
<div>
<button onClick={this.toggle}>toggle</button>
<TransitionGroup component="div">
{show && renderPhotos()}
</TransitionGroup>
</div>
);
}
}
在該示例中,我們?cè)谧咏M件 Photo 的 componentWillEnter 和 componentWillLeave 兩個(gè) hook 函數(shù)中為每個(gè)子組件添加了入場(chǎng)動(dòng)畫 enterAnim 和 離場(chǎng)動(dòng)畫 LeaveAnim 。在入場(chǎng)動(dòng)畫中,使用 TimeLineMax.from(target, duration, vars, delay) 方式建立時(shí)間軸動(dòng)畫,指定了每個(gè)子組件的動(dòng)畫移動(dòng)距離隨 id 增大而減小,延期時(shí)間隨著 id 增大而增大,離場(chǎng)動(dòng)畫中每個(gè)子組件的延期時(shí)間隨著 id 增大而減小,從而實(shí)現(xiàn)根據(jù)組件 id 不同具有不同的動(dòng)畫效果。實(shí)際使用時(shí),你可以根據(jù)需求對(duì)任一子組件添加不同的效果。該示例的效果如下圖所示:

在使用 TransitionGroup 時(shí),在 componentnWillAppear(callback) , componentnWillEntercallback) , componentnWillLeave(callback) 函數(shù)中一定要 在函數(shù)邏輯結(jié)束后調(diào)用 callback ,以保證 TransitionGroup 能正確維護(hù)子節(jié)點(diǎn)的狀態(tài)序列 。
結(jié)合 hook 實(shí)現(xiàn)動(dòng)畫可以支持各種復(fù)雜動(dòng)畫,如時(shí)間序列動(dòng)畫等,由于依賴第三方庫(kù),往往動(dòng)畫效果比較流暢,用戶體驗(yàn)較好。但是第三方庫(kù)的引入,需要開(kāi)發(fā)者額外學(xué)習(xí)對(duì)應(yīng)的 api,也提升了代碼復(fù)雜度。
五、其他第三方動(dòng)畫庫(kù)
此外,還有很多優(yōu)秀的第三方動(dòng)畫庫(kù),如 react-motion ,Animated, velocity-react 等,這些動(dòng)畫庫(kù)在使用時(shí)也各有千秋。
Animated
Animated 是一個(gè)跨平臺(tái)的動(dòng)畫庫(kù),兼容 React 和 React Native。由于在動(dòng)畫過(guò)程中,我們只關(guān)心動(dòng)畫的初始狀態(tài)、結(jié)束狀態(tài)和變化函數(shù),并不關(guān)心每個(gè)時(shí)刻元素屬性的具體值,所以 Animatied 采用聲明式的動(dòng)畫,通過(guò)它提供的特定方法計(jì)算 css 對(duì)象,并傳入 Animated.div 實(shí)現(xiàn)動(dòng)畫效果。
示例
我們使用 Animated 實(shí)現(xiàn)一個(gè)圖片翻轉(zhuǎn)的效果,代碼如下。
import React, { Component } from 'react';
import Animated from 'animated/lib/targets/react-dom';
export default class PhotoPreview extends Component {
constructor(props) {
super(props);
this.state = {
anim: new Animated.Value(0)
};
}
handleClick = () => {
const { anim } = this.state;
anim.stopAnimation(value => {
Animated.spring(anim, {
toValue: Math.round(value) + 1
}).start();
});
}
render() {
const { anim } = this.state;
const rotateDegree = anim.interpolate({
inputRange: [0, 4],
outputRange: ['0deg', '360deg']
});
return (
<div>
<button onClick={this.handleClick}>向右翻轉(zhuǎn)</button>
<Animated.div
style={{
transform: [{
rotate: rotateDegree
}]
}}
className="preivew-wrapper"
>
<img
alt="img"
src="http://img4.imgtn.bdimg.com/it/u=1032683424,3204785822&fm=214&gp=0.jpg"
/>
</Animated.div>
</div>
);
}
}
在該示例中,我們希望實(shí)現(xiàn)每點(diǎn)擊一次按鈕,圖片向右旋轉(zhuǎn)90°。在組件初始化時(shí)新建了一個(gè)初始值為 0 的 Animated 對(duì)象 this.state.anim ,在 render 函數(shù)中通過(guò)插值函數(shù) interpolate 根據(jù) Animated 對(duì)象的當(dāng)前值計(jì)算得到對(duì)應(yīng)的旋轉(zhuǎn)角度 rotateDegree 。我們假設(shè)每點(diǎn)擊一次按鈕, Animated 對(duì)象的值加 1,相應(yīng)地圖像轉(zhuǎn)動(dòng)90°,所以,設(shè)置 interpolate 函數(shù)的輸入?yún)^(qū)間為[0, 4],輸出區(qū)間為['0deg', '360deg']進(jìn)行線性插值。如果 Animated 對(duì)象當(dāng)前值為 2,對(duì)應(yīng)的旋轉(zhuǎn)角度就是 180deg。在組件渲染結(jié)構(gòu)中,需要使用 Animated.div 包裹動(dòng)畫節(jié)點(diǎn),并將變化的元素屬性封裝為 css 對(duì)象作為 stlye 傳入 Animated.div 中。在點(diǎn)擊事件中,考慮到按鈕可以多次連續(xù)點(diǎn)擊,我們首先使用 stopAnimation 停止當(dāng)前動(dòng)畫,并獲取 Animated 對(duì)象的當(dāng)前值 value ,隨后使用 Animated.spring 函數(shù)開(kāi)啟一次彈簧動(dòng)畫過(guò)程,從而實(shí)現(xiàn)一個(gè)流暢的動(dòng)畫效果。由于每次轉(zhuǎn)動(dòng)停止時(shí),我們希望圖片的翻轉(zhuǎn)角度都是90°的整數(shù)倍,所以需要對(duì) Animated.spring 的終止值進(jìn)行取整。最終我們實(shí)現(xiàn)了如下效果:

使用時(shí)需要注意一下幾點(diǎn):
Animated對(duì)象的值和其插值結(jié)果只能作用于Animated.div節(jié)點(diǎn);interpolate默認(rèn)會(huì)根據(jù)輸入?yún)^(qū)間和輸出區(qū)間進(jìn)行線性插值,如果輸入值超出輸入?yún)^(qū)間不受影響,插值結(jié)果默認(rèn)會(huì)根據(jù)輸出區(qū)間向外延展插值,可以通過(guò)設(shè)置extrapolate屬性限制插值結(jié)果區(qū)間。
Animated 在動(dòng)畫過(guò)程中不直接修改組件 state ,而是通過(guò)其新建對(duì)象的組件和方法直接修改元素的屬性,不會(huì)重復(fù)觸發(fā) render 函數(shù),是 React Native 中非常穩(wěn)定的動(dòng)畫庫(kù)。但是在 React 中存在低版本瀏覽器兼容問(wèn)題,且具有一定學(xué)習(xí)成本。
結(jié)語(yǔ)
當(dāng)我們?cè)?React 中實(shí)現(xiàn)動(dòng)畫時(shí),首先要考量動(dòng)畫的難易程度和使用場(chǎng)景,對(duì)于簡(jiǎn)單動(dòng)畫,優(yōu)先使用 css3 實(shí)現(xiàn),其次是基于 js 的時(shí)間間隔動(dòng)畫。如果是元素入場(chǎng)動(dòng)畫和離場(chǎng)動(dòng)畫,則建議結(jié)合 CSSTransitionGroup 或者 TransitionGroup 實(shí)現(xiàn)。當(dāng)要實(shí)現(xiàn)的動(dòng)畫效果較為復(fù)雜時(shí),不妨嘗試一些優(yōu)秀的第三方庫(kù),打開(kāi)精彩的動(dòng)效大門。
Ps. 本文所有示例代碼可訪問(wèn) github 查看
參考資料:
A Comparison of Animation Technologies
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 在React中寫一個(gè)Animation組件為組件進(jìn)入和離開(kāi)加上動(dòng)畫/過(guò)度效果
- 詳解如何給React-Router添加路由頁(yè)面切換時(shí)的過(guò)渡動(dòng)畫
- react-router 路由切換動(dòng)畫的實(shí)現(xiàn)示例
- react中使用css的7中方式(最全總結(jié))
- 在Create React App中使用CSS Modules的方法示例
- 在create-react-app中使用css modules的示例代碼
- 詳解使用create-react-app添加css modules、sasss和antd
- ReactJs設(shè)置css樣式的方法
- react使用CSS實(shí)現(xiàn)react動(dòng)畫功能示例
相關(guān)文章
React通過(guò)ref獲取子組件的數(shù)據(jù)和方法
這篇文章主要介紹了React如何通過(guò)ref獲取子組件的數(shù)據(jù)和方法,文中有詳細(xì)的總結(jié)內(nèi)容和代碼示例,具有一定的參考價(jià)值,需要的朋友可以參考下2023-10-10
React Grid Layout基礎(chǔ)使用示例教程
React Grid Layout是一個(gè)用于在React應(yīng)用程序中創(chuàng)建可拖拽和可調(diào)整大小的網(wǎng)格布局的庫(kù),通過(guò)使用React Grid Layout,我們可以輕松地創(chuàng)建自適應(yīng)的網(wǎng)格布局,并實(shí)現(xiàn)拖拽和調(diào)整大小的功能,本文介紹了React Grid Layout的基礎(chǔ)使用方法,感興趣的朋友一起看看吧2024-02-02
React超詳細(xì)分析useState與useReducer源碼
我正在處理的組件是表單的時(shí)間輸入。表單相對(duì)復(fù)雜,并且是動(dòng)態(tài)生成的,根據(jù)嵌套在其他數(shù)據(jù)中的數(shù)據(jù)顯示不同的字段。我正在用useReducer管理表單的狀態(tài),到目前為止效果很好2022-11-11
React使用hook如何實(shí)現(xiàn)數(shù)據(jù)雙向綁定
這篇文章主要介紹了React使用hook如何實(shí)現(xiàn)數(shù)據(jù)雙向綁定方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
react項(xiàng)目中redux的調(diào)試工具不起作用的解決
這篇文章主要介紹了react項(xiàng)目中redux的調(diào)試工具不起作用的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
React.memo函數(shù)中的參數(shù)示例詳解
這篇文章主要為大家介紹了React.memo函數(shù)中的參數(shù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
react-native使用leanclound消息推送的方法
這篇文章主要介紹了react-native使用leanclound消息推送的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08

