現(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 作為最近幾年比較流行的前端開發(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)畫效果,需要開發(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 的框架特性,可以概括為幾類:
最早,動(dòng)畫的實(shí)現(xiàn)都是依靠定時(shí)器 setInterval
, setTimeout
或者 requestAnimationFrame
(RAF) 直接修改 DOM 元素的屬性。不熟悉 React 特性的開發(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)象。此外,還需要開發(fā)者根據(jù)速度函數(shù)自己計(jì)算狀態(tài),比較復(fù)雜。
當(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 樹中的節(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)畫中的變化屬性。在這種情況下,開發(fā)者需要花費(fèi)大量精力來(lái)維護(hù)組件的動(dòng)畫邏輯,十分復(fù)雜繁瑣。
React 曾為開發(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í)需要開發(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)開啟的,使用時(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 樹中生成一個(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);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)是:
CSSTransitionGroup
缺點(diǎn)也十分明顯:
transitionName
,靈活性不夠;在實(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ù):
它們的觸發(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ù)的引入,需要開發(fā)者額外學(xué)習(xí)對(duì)應(yīng)的 api,也提升了代碼復(fù)雜度。
此外,還有很多優(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ù)開啟一次彈簧動(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í)成本。
當(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ù),打開精彩的動(dòng)效大門。
Ps. 本文所有示例代碼可訪問(wèn) github 查看
react-transition-group
react-gsap-enhancer
A Comparison of Animation Technologies
React Animations in Depth
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com