IT教程 ·

基于 React 完成一个 Transition 过渡动画组件

Nginx之常用基本配置(一)

过渡动画使 UI 更富有表现力而且易于运用。怎样运用 React 疾速的完成一个 Transition 过渡动画组件?

基本完成

完成一个基本的 CSS 过渡动画组件,经由过程切换 CSS 款式完成简朴的动画结果,也就是经由过程增加或移除某个 class 款式。因而须要给 Transition 组件增加一个 toggleClass 属性,标识要切换的 class 款式,再增加一个 action 属性完成款式切换,action 为 true 时增加 toggleClass 到动画元素上,action 为 false 时移除 toggleClass。

装置 classnames 插件:

npm install classnames --save-dev

是一个简朴的JavaScript实用程序,用于有条件地将 className 衔接在一起。

在 components 目录下新建一个 Transition 文件夹,并在该文件夹下新建一个 Transition.jsx 文件:

import React from 'react'
import classnames from 'classnames'

/**
 * css过渡动画组件
 *
 * @visibleName Transition 过渡动画
 */
class Transition extends React.Component {
  render() {
    const { children } = this.props
    const transition = (
      <div
        className={
          classnames({
            transition: true
          })
        }
        style={
          {
            position: 'relative',
            overflow: 'hidden'
          }
        }
      >
        <div
          className={
            classnames({
              'transition-wrapper': true
            })
          }
        >
          { children }
        </div>
      </div>
    )
    return transition
  }
}

export default Transition

这里运用了 ,在 JSX 中,运用 camelCase(小驼峰定名)来定义属性的称号,运用大括号“{}”嵌入任何有用的 。
如:

const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;

等价于:

const element = <h1>Hello, Josh Perez</h1>;

注重:
因为 JSX 语法上更靠近 JavaScript 而不是 HTML,所以 React DOM 运用 camelCase(小驼峰定名)来定义属性的称号,而不运用 HTML 属性称号的定名商定。
比方,JSX 里的 class 变成了 className,而 tabindex 则变成 tabIndex。

别的,在 React 中,
包含组件一切的子节点,即组件的入手下手标签和完毕标签之间的内容(与 Vue 中 slot 插槽类似)。比方:

<Button>默许按钮</Button>

在 Button 组件中猎取 props.children,就能够获得字符串“默许按钮”。

接下来,在 Transition 文件夹下新建一个 index.js,导出 Transition 组件:

import Transition from './Transition.jsx'

export { Transition }

export default Transition

然后,在 Transition.jsx 文件中为组件增加 props 搜检并设置 action 的默许值:

import PropTypes from 'prop-types'

const propTypes = {
  /** 实行动画 */
  action: PropTypes.bool,
  /** 切换的css动画的class称号 */
  toggleClass: PropTypes.string
}

const defaultProps = {
  action: false
}

这里运用了 完成运行时范例搜检。

注重:
prop-types 是一个运行时范例搜检东西,也是 脚手架默许设置的运行时范例搜检东西,运用时直接引入即可,无需装置。

完全的 Transition 组件代码以下:

import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'

const propTypes = {
  /** 实行动画 */
  action: PropTypes.bool,
  /** 切换的css动画的class称号 */
  toggleClass: PropTypes.string
}

const defaultProps = {
  action: false
}

/**
 * css过渡动画组件
 *
 * @visibleName Transition 过渡动画
 */
class Transition extends React.Component {

  static propTypes = propTypes

  static defaultProps = defaultProps

  render() {
    const {
      className,
      action,
      toggleClass,
      children
    } = this.props
    const transition = (
      <div
        className={
          classnames({
            transition: true
          })
        }
        style={
          {
            position: 'relative',
            overflow: 'hidden'
          }
        }
      >
        <div
          className={
            classnames({
              'transition-wrapper': true,
              [className]: className,
              [toggleClass]: action && toggleClass
            })
          }
        >
          { children }
        </div>
      </div>
    )
    return transition
  }
}

export default Transition

如今,能够运用我们的 Transition 组件了。

CSS 代码以下:

.fade {
  transition: opacity 0.15s linear;
}

.fade:not(.show) {
  opacity: 0;
}

JS 代码以下:

import React from 'react';
import Transition from './Transition';

class Anime extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      action: true
    }
  }
  
  render () {
    const btnText = this.state.action ? '淡出' : '淡入'
    return (
      <div>
        <Transition
          className="fade"
          toggleClass="show"
          action={ this.state.action }
        >
          淡入淡出
        </Transition>
        <button
          style={{ marginTop: '20px' }}
          onClick={() => this.setState({ action: !this.state.action })}
        >
          { btnText }
        </button>
      </div>
    )
  }
}

然后,在你须要该动画的处所运用 Anime 组件即可。

完成 Animate.css 兼容

是一款壮大的预设 CSS3 动画库。接下来,完成在 Transition 组件中运用 Animate.css 完成壮大的 CSS3 动画。

因为 Animate.css 动画在进入动画和脱离动画一般运用两个结果相反的 class 款式,因而,须要给 Transition 组件增加 enterClass 和 leaveClass 两个属性,完成动画切换。

import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'

const propTypes = {
  /** 实行动画 */
  action: PropTypes.bool,
  /** 切换的css动画的class称号 */
  toggleClass: PropTypes.string,
  /** 进入动画的class称号,存在 toggleClass 时无效 */
  enterClass: PropTypes.string,
  /** 脱离动画的class称号,存在 toggleClass 时无效 */
  leaveClass: PropTypes.string
}

const defaultProps = {
  action: false
}

/**
 * css过渡动画组件
 *
 * @visibleName Transition 过渡动画
 */
class Transition extends React.Component {

  static propTypes = propTypes

  static defaultProps = defaultProps

  render() {
    const {
      className,
      action,
      toggleClass,
      enterClass,
      leaveClass,
      children
    } = this.props
    return (
      <div
        className={
          classnames({
            transition: true
          })
        }
        style={
          {
            position: 'relative',
            overflow: 'hidden'
          }
        }
      >
        <div
          className={
            classnames({
              'transition-wrapper': true,
              [className]: className,
              [toggleClass]: action && toggleClass,
              [enterClass]: !toggleClass && action && enterClass,
              [leaveClass]: !toggleClass && !action && leaveClass,
            })
          }
        >
          { children }
        </div>
      </div>
    )
  }
}

export default Transition

注重:
因为 toggleClass 实用于那些进入动画与脱离动画切换雷同 class 款式的状况,而 enterClass 和 leaveClass 实用于那些进入动画与脱离动画切换差别的 class 款式的状况,所以,他们与 toggleClass 不能共存。

接下来,就能够试一试到场 Animate.css 后的 Transition 组件:

import React from 'react';
import 'animate.css';

class Anime extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      action: true
    }
  }
  
  render () {
    return (
      <div>
        <Transition
          className="animated"
          enterClass="bounceInLeft"
          leaveClass="bounceOutLeft"
          action={ this.state.action }
        >
          弹入弹出
        </Transition>
        <utton
          style={{ marginTop: '20px' }}
          onClick={() => this.setState({ action: !this.state.action })}
        >
          { this.state.action ? '弹出' : '弹入' }
        </utton>
      </div>
    )
  }
}

功用扩大

经由过程上面的完成,Transition 组件能实用大部分场景,然则功用不够雄厚。因而,接下来就须要扩大 Transition 的接口。动画一般能够设置耽误时候,播放时长,播放次数等属性。因而,须要给 Transition 增加这些属性,来雄厚设置动画。

增加以下 props 属性,并设置默许值:

const propTypes = {
  ...,
  /** 动画耽误实行时候 */
  delay: PropTypes.string,
  /** 动画实行时候长度 */
  duration: PropTypes.string,
  /** 动画实行次数,只在实行 CSS3 动画时有用 */
  count: PropTypes.number,
  /** 动画缓动函数 */
  easing: PropTypes.oneOf([
    'linear',
    'ease',
    'ease-in',
    'ease-out',
    'ease-in-out'
  ]),
  /** 是不是强迫轮番反向播放动画,count 为 1 时无效 */
  reverse: PropTypes.bool
}

const defaultProps = {
  count: 1,
  reverse: false
}

依据 props 设置款式:

// 动画款式
const styleText = (() => {
  let style = {}
  // 设置耽误时长
  if (delay) {
    style.transitionDelay = delay
    style.animationDelay = delay
  }
  // 设置播放时长
  if (duration) {
    style.transitionDuration = duration
    style.animationDuration = duration
  }
  // 设置播放次数
  if (count) {
    style.animationIterationCount = count
  }
  // 设置缓动函数
  if (easing) {
    style.transitionTimingFunction = easing
    style.animationTimingFunction = easing
  }
  // 设置动画方向
  if (reverse) {
    style.animationDirection = 'alternate'
  }
  return style
})()

完全代码以下:

import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'

const propTypes = {
  /** 实行动画 */
  action: PropTypes.bool,
  /** 切换的css动画的class称号 */
  toggleClass: PropTypes.string,
  /** 进入动画的class称号,存在 toggleClass 时无效 */
  enterClass: PropTypes.string,
  /** 脱离动画的class称号,存在 toggleClass 时无效 */
  leaveClass: PropTypes.string,
  /** 动画耽误实行时候 */
  delay: PropTypes.string,
  /** 动画实行时候长度 */
  duration: PropTypes.string,
  /** 动画实行次数,只在实行 CSS3 动画时有用 */
  count: PropTypes.number,
  /** 动画缓动函数 */
  easing: PropTypes.oneOf([
    'linear',
    'ease',
    'ease-in',
    'ease-out',
    'ease-in-out'
  ]),
  /** 是不是强迫轮番反向播放动画,count 为 1 时无效 */
  reverse: PropTypes.bool
}

const defaultProps = {
  action: false,
  count: 1,
  reverse: false
}

/**
 * css过渡动画组件
 *
 * @visibleName Transition 过渡动画
 */
class Transition extends React.Component {

  static propTypes = propTypes

  static defaultProps = defaultProps

  render() {
    const {
      className,
      action,
      toggleClass,
      enterClass,
      leaveClass,
      delay,
      duration,
      count,
      easing,
      reverse,
      children
    } = this.props

    // 动画款式
    const styleText = (() => {
      let style = {}
      // 设置耽误时长
      if (delay) {
        style.transitionDelay = delay
        style.animationDelay = delay
      }
      // 设置播放时长
      if (duration) {
        style.transitionDuration = duration
        style.animationDuration = duration
      }
      // 设置播放次数
      if (count) {
        style.animationIterationCount = count
      }
      // 设置缓动函数
      if (easing) {
        style.transitionTimingFunction = easing
        style.animationTimingFunction = easing
      }
      // 设置动画方向
      if (reverse) {
        style.animationDirection = 'alternate'
      }
      return style
    })()

    return (
      <div
        className={
          classnames({
            transition: true
          })
        }
        style={
          {
            position: 'relative',
            overflow: 'hidden'
          }
        }
      >
        <div
          className={
            classnames({
              'transition-wrapper': true,
              [className]: className,
              [toggleClass]: action && toggleClass,
              [enterClass]: !toggleClass && action && enterClass,
              [leaveClass]: !toggleClass && !action && leaveClass,
            })
          }
          style={ styleText }
        >
          { children }
        </div>
      </div>
    )
  }
}

export default Transition

这里为 Transition 增加了以下设置属性:

  • delay:划定在动画入手下手之前的耽误。
  • duration:划定完成动画所消费的时候,以秒或毫秒计。
  • count:划定动画应当播放的次数。
  • easing:划定动画的速率曲线。
  • reverse:划定是不是应当轮番反向播放动画。

现在,Transition 的功用已相称雄厚,能够很邃密的掌握 CSS3 动画。

优化

这一步,我们须要针对 Transition 组件进一步优化,重要包含动画完毕的监听、卸载组件以及兼容。

增加以下 props 属性,并设置默许值:

const propTypes = {
  ...,
  /** 动画完毕的回调 */
  onEnd: PropTypes.func,
  /** 脱离动画完毕时卸载元素 */
  exist: PropTypes.bool
}

const defaultProps = {
  ...,
  reverse: false,
  exist: false
}

处置惩罚动画完毕的监听事宜:

/**
 * css过渡动画组件
 *
 * @visibleName Transition 过渡动画
 */
class Transition extends React.Component {

  ...

  onEnd = e => {
    const { onEnd, action, exist } = this.props
    if (onEnd) {
      onEnd(e)
    }
    // 卸载 DOM 元素
    if (!action && exist) {
      const node = e.target.parentNode
      node.parentNode.removeChild(node)
    }
  }

  /**
   * 对动画完毕事宜 onEnd 回调的处置惩罚函数
   *
   * @param {string} type - 事宜解绑定范例: add - 绑定事宜,remove - 移除事宜绑定
   */
  handleEndListener (type = 'add') {
    const el = ReactDOM.findDOMNode(this).querySelector('.transition-wrapper')
    const events = ['animationend', 'transitionend']
    events.forEach(ev => {
      el[`${type}EventListener`](ev, this.onEnd, false)
    })
  }

  componentDidMount () {
    this.handleEndListener()
  }

  componentWillUnmount () {
    const { action, exist } = this.props
    if (!action && exist) {
      this.handleEndListener('remove')
    }
  }

  render () {
    ...
  }
}

这里运用到两个生命周期函数 componentDidMount 和 componentWillUnmount,关于 React 生命周期的引见请移步。

供应了可在 React 运用中运用的 DOM 要领。

猎取兼容性的 animationend 事宜和 transitionend 事宜。差别的浏览器请求运用差别的前缀,因为火狐和IE都已支撑了这两个事宜,因而,只需针对 webkit 内核浏览器举行兼容的 webkitTransitionEnd 事宜检测。检测函数代码以下:

/**
 * 浏览器兼容事宜检测函数
 *
 * @param {node} el - 触发事宜的 DOM 元素
 * @param {array} events - 大概的事宜范例
 * @returns {*}
 */
const whichEvent = (el, events) => {
  const len = events.length
  for (var i = 0; i < len; i++) {
    if (el.style[i]) {
      return events[i];
    }
  }
}

修正 handleEndListener 函数:

/**
 * css过渡动画组件
 *
 * @visibleName Transition 过渡动画
 */
class Transition extends React.Component {

  ...

  /**
   * 对动画完毕事宜 onEnd 回调的处置惩罚函数
   *
   * @param {string} type - 事宜解绑定范例: add - 绑定事宜,remove - 移除事宜绑定
   */
  handleEndListener (type = 'add') {
    const el = ReactDOM.findDOMNode(this).querySelector('.transition-wrapper')
    const events = ['AnimationEnd', 'TransitionEnd']
    events.forEach(ev => {
      const eventType = whichEvent(el, [ev.toLowerCase(), `webkit${ev}`])
      el[`${type}EventListener`](eventType, this.onEnd, false)
    })
  }

  ...

}

到这里,我们完成了全部 Transition 组件的开发,完全代码以下:

import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import ReactDOM from 'react-dom'

const propTypes = {
  /** 实行动画 */
  action: PropTypes.bool,
  /** 切换的css动画的class称号 */
  toggleClass: PropTypes.string,
  /** 进入动画的class称号,存在 toggleClass 时无效 */
  enterClass: PropTypes.string,
  /** 脱离动画的class称号,存在 toggleClass 时无效 */
  leaveClass: PropTypes.string,
  /** 动画耽误实行时候 */
  delay: PropTypes.string,
  /** 动画实行时候长度 */
  duration: PropTypes.string,
  /** 动画实行次数,只在实行 CSS3 动画时有用 */
  count: PropTypes.number,
  /** 动画缓动函数 */
  easing: PropTypes.oneOf([
    'linear',
    'ease',
    'ease-in',
    'ease-out',
    'ease-in-out'
  ]),
  /** 是不是强迫轮番反向播放动画,count 为 1 时无效 */
  reverse: PropTypes.bool,
  /** 动画完毕的回调 */
  onEnd: PropTypes.func,
  /** 脱离动画完毕时卸载元素 */
  exist: PropTypes.bool
}

const defaultProps = {
  action: false,
  count: 1,
  reverse: false,
  exist: false
}

/**
 * 浏览器兼容事宜检测函数
 *
 * @param {node} el - 触发事宜的 DOM 元素
 * @param {array} events - 大概的事宜范例
 * @returns {*}
 */
const whichEvent = (el, events) => {
  const len = events.length
  for (var i = 0; i < len; i++) {
    if (el.style[i]) {
      return events[i];
    }
  }
}

/**
 * css过渡动画组件
 *
 * @visibleName Transition 过渡动画
 */
class Transition extends React.Component {

  static propTypes = propTypes

  static defaultProps = defaultProps

  onEnd = e => {
    const { onEnd, action, exist } = this.props
    if (onEnd) {
      onEnd(e)
    }
    // 卸载 DOM 元素
    if (!action && exist) {
      const node = e.target.parentNode
      node.parentNode.removeChild(node)
    }
  }

  /**
   * 对动画完毕事宜 onEnd 回调的处置惩罚函数
   *
   * @param {string} type - 事宜解绑定范例: add - 绑定事宜,remove - 移除事宜绑定
   */
  handleEndListener (type = 'add') {
    const el = ReactDOM.findDOMNode(this).querySelector('.transition-wrapper')
    const events = ['AnimationEnd', 'TransitionEnd']
    events.forEach(ev => {
      const eventType = whichEvent(el, [ev.toLowerCase(), `webkit${ev}`])
      el[`${type}EventListener`](eventType, this.onEnd, false)
    })
  }

  componentDidMount () {
    this.handleEndListener()
  }

  componentWillUnmount() {
    const { action, exist } = this.props
    if (!action && exist) {
      this.handleEndListener('remove')
    }
  }

  render () {
    const {
      className,
      action,
      toggleClass,
      enterClass,
      leaveClass,
      delay,
      duration,
      count,
      easing,
      reverse,
      children
    } = this.props

    // 动画款式
    const styleText = (() => {
      let style = {}
      // 设置耽误时长
      if (delay) {
        style.transitionDelay = delay
        style.animationDelay = delay
      }
      // 设置播放时长
      if (duration) {
        style.transitionDuration = duration
        style.animationDuration = duration
      }
      // 设置播放次数
      if (count) {
        style.animationIterationCount = count
      }
      // 设置缓动函数
      if (easing) {
        style.transitionTimingFunction = easing
        style.animationTimingFunction = easing
      }
      // 设置动画方向
      if (reverse) {
        style.animationDirection = 'alternate'
      }
      return style
    })()

    const transition = (
      <div
        className={
          classnames({
            transition: true
          })
        }
        style={
          {
            position: 'relative',
            overflow: 'hidden'
          }
        }
      >
        <div
          className={
            classnames({
              'transition-wrapper': true,
              [className]: className,
              [toggleClass]: action && toggleClass,
              [enterClass]: !toggleClass && action && enterClass,
              [leaveClass]: !toggleClass && !action && leaveClass,
            })
          }
          style={ styleText }
        >
          { children }
        </div>
      </div>
    )

    return transition
  }
}

export default Transition

 

只有努力了,你才能成为想要的样子

参与评论