React中如何不使用插件實現組件出現或消失動畫
- 2019 年 12 月 4 日
- 筆記
本文作者:IMWeb 結一 原文出處:IMWeb社區 未經同意,禁止轉載
首先React本身是有動畫插件的React.addons.TransitionGroup,當你使用該組件時,會添加對應的動畫生命周期函數來控制動畫,分別為componentWillEnter,componentDidEnter,componentWillLeave,componentDidLeave。而更高級點的ReactCSSTransitionGroup則是ReactTransitionGroup是基於ReactTransitionGroup的,在React組件進入或者離開DOM的時候,它是一種簡單地執行CSS過渡和動畫的方式。
今天我們來探討的是另一種實現方式,而非使用官方的插件。先拋開React,我們一般實現動畫都是添加或刪除對應的動畫class,這是因為DOM結構本身就存在,所以可以任意操作,而React則不同,每個組件都是有生命周期的,componentDidMount則是組件掛載到DOM結構,而componentWillUnmount則在組件被移除DOM前調用。所以我們可以使用外包一層,把控制動畫的責任落在這個已經存在的DOM結構上。
簡單示意如下:CustomContent
為React組件,這裡要實現的就是它的出現或消失動畫,.animate-wrap
為包裹的外層
class Page extends Component { render() { return ( <div className="page"> <header className="header">Header</header> <div className="animate-wrap"> <CustomContent isShow={this.props.contentIsShow} /> </div> </div> ) } }
下面我們繼續構造,當content隱藏的時候,.animate-wrap
隱藏,當content顯示的時候,顯示.animate-wrap
並為其添加class動畫
class Page extends Component { render() { let { contentIsShow, toggleContent } = this.props; return ( <div className="page"> <header className="header">Header</header> <div ref="animateWrap" className={ contentIsShow ? "animate-wrap active down-in" : "animate-wrap"}> <CustomContent isShow={ contentIsShow } clickHandler={ this.clickHandler.bind(this) } /> </div> </div> ) } }
關鍵css大概如下(動畫設計可參看移動端重構實戰系列4——進入離開動畫):
.animate-wrap{ display: none; // 默認不顯示 &.active{ display: block; // active狀態顯示 } } // 進入動畫 .down-in{ animation: downIn 0.3s both; } @keyframes downIn{ from{ transform: translate(0, 100%); opacity: 0; } to{ transform: translate(0, 0); opacity: 1; } } // 離開動畫 .down-out{ animation: downOut 0.3s both; } @keyframes downOut{ from{ transform: translate(0, 0); opacity: 1; } to{ transform: translate(0, 100%); opacity: 0; } }
進入動畫之後,動畫結束之時應該去掉動畫的class.donw-in
,這就得使用DOM事件來處理了,在componentDidMount中添加監聽事件,而在componentWillUnmount中移除監聽事件
而最後content消失的時候則需要先添加down-out
class,再在動畫結束之後移除該class,並且改變contentIsShow
的值
// 判斷使用哪個end事件 function whichEndEvent() { var k el = document.createElement('div'); var animations = { "animation" : "animationend", "WebkitAnimation": "webkitAnimationEnd" } for(k in animations) { if(el.style[k] !== undefined) { return animations[k]; } } } //在前面render的基礎上添加以下動畫控制 class Page extends Component { custructor(props){ supor(props); this.animateEnd = whichEndEvent(); } componentDidMount() { // 監聽動畫結束事件,使用onAnimationEnd時,直接砍掉 let dWrap = ReactDOM.findDOMNode(this.refs.animateWrap); dWrap.addEventListener(this.animateEnd , this.removeAnimateClass) } componentWillUnmount() { // 移除動畫結束事件,使用onAnimationEnd時,直接砍掉 let dWrap = ReactDOM.findDOMNode(this.refs.animateWrap); dWrap.removeEventListener(this.animateEnd , this.removeAnimateClass) } removeAnimateClass() { let dWrap = ReactDOM.findDOMNode(this.refs.animateWrap); dWrap.classList.remove('down-in', 'down-out'); // 如果為離開,則觸發action toggleContent,設置contentIsShow為false,隱藏content if(dWrap.classLIst.contains('down-out')){ this.props.toggleContent(!this.props.contentIsShow); } } clickHandler() { // 觸發離開,先添加動畫class down-out,並在動畫結束後調用想用的action let dWrap = ReactDOM.findDOMNode(this.refs.animateWrap); dWrap.classList.add('down-out'); } }
onAnimationEnd
2016-09-14更新
偶爾機會,發現React事件中已經有了onAnimationEnd
,所以上面的DOM事件監聽有點瑣碎,可以直接砍掉,在.animate-wrap
上添加 即可
class Page extends Component { render() { let { contentIsShow, toggleContent } = this.props; return ( <div className="page"> <header className="header">Header</header> <div ref="animateWrap" onAnimationEnd={ this.removeAnimateClass.bind(this) } className={ contentIsShow ? "animate-wrap active down-in" : "animate-wrap"}> <CustomContent isShow={ contentIsShow } clickHandler={ this.clickHandler.bind(this) } /> </div> </div> ) } }