­

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-outclass,再在動畫結束之後移除該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>          )      }  }