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> ) } }