溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

React怎么實(shí)現(xiàn)一個(gè)Transition過(guò)渡動(dòng)畫組件

發(fā)布時(shí)間:2022-02-23 14:32:17 來(lái)源:億速云 閱讀:169 作者:小新 欄目:開發(fā)技術(shù)

這篇文章將為大家詳細(xì)講解有關(guān)React怎么實(shí)現(xiàn)一個(gè)Transition過(guò)渡動(dòng)畫組件,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

一、基本實(shí)現(xiàn)

我們?cè)趯?shí)現(xiàn)基礎(chǔ)的過(guò)度動(dòng)畫組件,需要通過(guò)切換CSS樣式實(shí)現(xiàn)簡(jiǎn)單的動(dòng)畫效果。

首先我們安裝 classnames 插件:

npm install classnames --save-dev

而且 classnaems 是一個(gè)簡(jiǎn)單的JavaScript實(shí)用程序,用于有條件地將 classnames 連接一起。那么我們將 component 目錄新建一個(gè) Transition 文件夾,并在文件夾中新建一個(gè) Transition.jsx文件,代碼如下:

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

/**
 * css過(guò)渡動(dòng)畫組件
 *
 * @visibleName Transition 過(guò)渡動(dòng)畫
 */
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

在文件中我們通過(guò)使用小駝峰來(lái)定義屬性定義名稱;而且我們?cè)谖募惺褂?JSX 語(yǔ)法,我們來(lái)看看如下案例代碼:

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

這個(gè)代碼等價(jià)如下這串代碼:

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

當(dāng)然在使用 JSX 語(yǔ)法的時(shí)候我們還是要注意的,因?yàn)樵?JSX 語(yǔ)法中會(huì)更接近 JavaScript 而不是 html ,所以在 React DOM 中我們使用 小駝峰來(lái)進(jìn)行命名,而不使用 html 屬性名稱約定。

除此之外在 React 中的 props.children 包含組件所有的子節(jié)點(diǎn),即組件開始標(biāo)簽和結(jié)束標(biāo)簽之間的內(nèi)容,如下案例所示:

<Button>默認(rèn)按鈕</Button>

Button 組件中獲取 props.children,就可以得到字符串“默認(rèn)按鈕”。

那么接下來(lái)我們?cè)?Transition 文件夾下新建一個(gè) index.js ,導(dǎo)出 Transition 組件,代碼如下所示:

import Transition from './Transition.jsx'
export { Transition }
export default Transition

完成之后,在 Transition.jsx 文件中為組件添加 props 檢查并設(shè)置 action 默認(rèn)值,代碼如下所示:

import PropTypes from 'prop-types'
const propTypes = {
  /** 執(zhí)行動(dòng)畫 */
  action: PropTypes.bool,
  /** 切換的css動(dòng)畫的class名稱 */
  toggleClass: PropTypes.string
}
const defaultProps = {
  action: false
}

這時(shí)候我們使用 prop-ty.pes實(shí)現(xiàn)運(yùn)行時(shí)類型檢查。但是需要注意的是 prop-ty.pes是一個(gè)運(yùn)行時(shí);類型檢查工具,我們來(lái)看看完整的 Transition 組件代碼如下所示:

import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
const propTypes = {
  /** 執(zhí)行動(dòng)畫 */
  action: PropTypes.bool,
  /** 切換的css動(dòng)畫的class名稱 */
  toggleClass: PropTypes.string
}
const defaultProps = {
  action: false
}
/**
 * css過(guò)渡動(dòng)畫組件
 *
 * @visibleName Transition 過(guò)渡動(dòng)畫
 */
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

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

這樣子我們就只需要在需要使用動(dòng)畫的地方來(lái)進(jìn)行使用 Anime 組件就可以了。


二、實(shí)現(xiàn)Animate.css兼容

我們都知道 Animate.css 是一款強(qiáng)大的預(yù)設(shè) CSS3 動(dòng)畫庫(kù)。由于在進(jìn)入動(dòng)畫和離開動(dòng)畫通常在使用這兩個(gè)效果相反的 class 樣式,所以我們需要給我們的 Transition 組件添加 enterClassleaveClass 兩個(gè)屬性來(lái)實(shí)現(xiàn)動(dòng)畫的切換,代碼如下所示:

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

const propTypes = {
  /** 執(zhí)行動(dòng)畫 */
  action: PropTypes.bool,
  /** 切換的css動(dòng)畫的class名稱 */
  toggleClass: PropTypes.string,
  /** 進(jìn)入動(dòng)畫的class名稱,存在 toggleClass 時(shí)無(wú)效 */
  enterClass: PropTypes.string,
  /** 離開動(dòng)畫的class名稱,存在 toggleClass 時(shí)無(wú)效 */
  leaveClass: PropTypes.string
}

const defaultProps = {
  action: false
}

/**
 * css過(guò)渡動(dòng)畫組件
 *
 * @visibleName Transition 過(guò)渡動(dòng)畫
 */
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

當(dāng)然我們還是要注意一下,由于 toggleClass 適用于那些進(jìn)入動(dòng)畫與離開動(dòng)畫切換相同 class 樣式的情況,而且 enterClassleaveClass 使用那些進(jìn)入動(dòng)畫和離開動(dòng)畫切換不同的 class 樣式的情況,所以,他們和 toggleClass 不能共存。

那么我們接下來(lái)就嘗試下加入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>
    )
  }
}

三、功能擴(kuò)展

通過(guò)上面的方法實(shí)現(xiàn)之后我們知道 Transition 組件是可以適用在很多的場(chǎng)景中的,但是功能不是很豐富,所以就需要擴(kuò)展 Transition 的接口。首先我們來(lái)添加 props 屬性,并設(shè)置默認(rèn)值,代碼如下所示:

const propTypes = {
  ...,
  /** 動(dòng)畫延遲執(zhí)行時(shí)間 */
  delay: PropTypes.string,
  /** 動(dòng)畫執(zhí)行時(shí)間長(zhǎng)度 */
  duration: PropTypes.string,
  /** 動(dòng)畫執(zhí)行次數(shù),只在執(zhí)行 CSS3 動(dòng)畫時(shí)有效 */
  count: PropTypes.number,
  /** 動(dòng)畫緩動(dòng)函數(shù) */
  easing: PropTypes.oneOf([
    'linear',
    'ease',
    'ease-in',
    'ease-out',
    'ease-in-out'
  ]),
  /** 是否強(qiáng)制輪流反向播放動(dòng)畫,count 為 1 時(shí)無(wú)效 */
  reverse: PropTypes.bool
}

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

根據(jù) props 設(shè)置樣式,代碼如下所示:

// 動(dòng)畫樣式
const styleText = (() => {
  let style = {}
  // 設(shè)置延遲時(shí)長(zhǎng)
  if (delay) {
    style.transitionDelay = delay
    style.animationDelay = delay
  }
  // 設(shè)置播放時(shí)長(zhǎng)
  if (duration) {
    style.transitionDuration = duration
    style.animationDuration = duration
  }
  // 設(shè)置播放次數(shù)
  if (count) {
    style.animationIterationCount = count
  }
  // 設(shè)置緩動(dòng)函數(shù)
  if (easing) {
    style.transitionTimingFunction = easing
    style.animationTimingFunction = easing
  }
  // 設(shè)置動(dòng)畫方向
  if (reverse) {
    style.animationDirection = 'alternate'
  }
  return style
})()

完整代碼如下所示:

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

const propTypes = {
  /** 執(zhí)行動(dòng)畫 */
  action: PropTypes.bool,
  /** 切換的css動(dòng)畫的class名稱 */
  toggleClass: PropTypes.string,
  /** 進(jìn)入動(dòng)畫的class名稱,存在 toggleClass 時(shí)無(wú)效 */
  enterClass: PropTypes.string,
  /** 離開動(dòng)畫的class名稱,存在 toggleClass 時(shí)無(wú)效 */
  leaveClass: PropTypes.string,
  /** 動(dòng)畫延遲執(zhí)行時(shí)間 */
  delay: PropTypes.string,
  /** 動(dòng)畫執(zhí)行時(shí)間長(zhǎng)度 */
  duration: PropTypes.string,
  /** 動(dòng)畫執(zhí)行次數(shù),只在執(zhí)行 CSS3 動(dòng)畫時(shí)有效 */
  count: PropTypes.number,
  /** 動(dòng)畫緩動(dòng)函數(shù) */
  easing: PropTypes.oneOf([
    'linear',
    'ease',
    'ease-in',
    'ease-out',
    'ease-in-out'
  ]),
  /** 是否強(qiáng)制輪流反向播放動(dòng)畫,count 為 1 時(shí)無(wú)效 */
  reverse: PropTypes.bool
}

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

/**
 * css過(guò)渡動(dòng)畫組件
 *
 * @visibleName Transition 過(guò)渡動(dòng)畫
 */
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

    // 動(dòng)畫樣式
    const styleText = (() => {
      let style = {}
      // 設(shè)置延遲時(shí)長(zhǎng)
      if (delay) {
        style.transitionDelay = delay
        style.animationDelay = delay
      }
      // 設(shè)置播放時(shí)長(zhǎng)
      if (duration) {
        style.transitionDuration = duration
        style.animationDuration = duration
      }
      // 設(shè)置播放次數(shù)
      if (count) {
        style.animationIterationCount = count
      }
      // 設(shè)置緩動(dòng)函數(shù)
      if (easing) {
        style.transitionTimingFunction = easing
        style.animationTimingFunction = easing
      }
      // 設(shè)置動(dòng)畫方向
      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

在這里我們來(lái)看下相關(guān)的 Transition 增加的屬性:

  • delay:規(guī)定在動(dòng)畫開始之前的延遲。

  • duration:規(guī)定完成動(dòng)畫所花費(fèi)的時(shí)間,以秒或毫秒計(jì)。

  • count:規(guī)定動(dòng)畫應(yīng)該播放的次數(shù)。

  • easing:規(guī)定動(dòng)畫的速度曲線。

  • reverse:規(guī)定是否應(yīng)該輪流反向播放動(dòng)畫。


四、優(yōu)化

那么接下來(lái)我們來(lái)對(duì) Transition 來(lái)進(jìn)行一個(gè)優(yōu)化,我們主要的是動(dòng)畫監(jiān)聽、卸載組件以及其他的相關(guān)兼容問(wèn)題。那么我們?cè)诖a中添加 props 屬性,并且設(shè)置默認(rèn)值。代碼如下所示:

const propTypes = {
  ...,
  /** 動(dòng)畫結(jié)束的回調(diào) */
  onEnd: PropTypes.func,
  /** 離開動(dòng)畫結(jié)束時(shí)卸載元素 */
  exist: PropTypes.bool
}

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

接下來(lái)進(jìn)行動(dòng)畫結(jié)束監(jiān)聽的事件代碼:

/**
 * css過(guò)渡動(dòng)畫組件
 *
 * @visibleName Transition 過(guò)渡動(dòng)畫
 */
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)
    }
  }

  /**
   * 對(duì)動(dòng)畫結(jié)束事件 onEnd 回調(diào)的處理函數(shù)
   *
   * @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 這兩個(gè)生命周期函數(shù)。

react-dom 中還為我們提供了可以在 React 應(yīng)用中使用的 DOM 方法。我們通過(guò)獲取兼容性 animationend 和transitionend 事件。檢驗(yàn)函數(shù)方法的代碼如下所示:

/**
 * 瀏覽器兼容事件檢測(cè)函數(shù)
 *
 * @param {node} el - 觸發(fā)事件的 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 函數(shù)代碼如下所示:

/**
 * css過(guò)渡動(dòng)畫組件
 *
 * @visibleName Transition 過(guò)渡動(dòng)畫
 */
class Transition extends React.Component {

  ...

  /**
   * 對(duì)動(dòng)畫結(jié)束事件 onEnd 回調(diào)的處理函數(shù)
   *
   * @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)
    })
  }

  ...

}

那么到這里之后我們就完成了整個(gè) Transition 組件的開發(fā),相關(guān)完整代碼如下所示:

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

const propTypes = {
  /** 執(zhí)行動(dòng)畫 */
  action: PropTypes.bool,
  /** 切換的css動(dòng)畫的class名稱 */
  toggleClass: PropTypes.string,
  /** 進(jìn)入動(dòng)畫的class名稱,存在 toggleClass 時(shí)無(wú)效 */
  enterClass: PropTypes.string,
  /** 離開動(dòng)畫的class名稱,存在 toggleClass 時(shí)無(wú)效 */
  leaveClass: PropTypes.string,
  /** 動(dòng)畫延遲執(zhí)行時(shí)間 */
  delay: PropTypes.string,
  /** 動(dòng)畫執(zhí)行時(shí)間長(zhǎng)度 */
  duration: PropTypes.string,
  /** 動(dòng)畫執(zhí)行次數(shù),只在執(zhí)行 CSS3 動(dòng)畫時(shí)有效 */
  count: PropTypes.number,
  /** 動(dòng)畫緩動(dòng)函數(shù) */
  easing: PropTypes.oneOf([
    'linear',
    'ease',
    'ease-in',
    'ease-out',
    'ease-in-out'
  ]),
  /** 是否強(qiáng)制輪流反向播放動(dòng)畫,count 為 1 時(shí)無(wú)效 */
  reverse: PropTypes.bool,
  /** 動(dòng)畫結(jié)束的回調(diào) */
  onEnd: PropTypes.func,
  /** 離開動(dòng)畫結(jié)束時(shí)卸載元素 */
  exist: PropTypes.bool
}

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

/**
 * 瀏覽器兼容事件檢測(cè)函數(shù)
 *
 * @param {node} el - 觸發(fā)事件的 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過(guò)渡動(dòng)畫組件
 *
 * @visibleName Transition 過(guò)渡動(dòng)畫
 */
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)
    }
  }

  /**
   * 對(duì)動(dòng)畫結(jié)束事件 onEnd 回調(diào)的處理函數(shù)
   *
   * @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

    // 動(dòng)畫樣式
    const styleText = (() => {
      let style = {}
      // 設(shè)置延遲時(shí)長(zhǎng)
      if (delay) {
        style.transitionDelay = delay
        style.animationDelay = delay
      }
      // 設(shè)置播放時(shí)長(zhǎng)
      if (duration) {
        style.transitionDuration = duration
        style.animationDuration = duration
      }
      // 設(shè)置播放次數(shù)
      if (count) {
        style.animationIterationCount = count
      }
      // 設(shè)置緩動(dòng)函數(shù)
      if (easing) {
        style.transitionTimingFunction = easing
        style.animationTimingFunction = easing
      }
      // 設(shè)置動(dòng)畫方向
      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

關(guān)于“React怎么實(shí)現(xiàn)一個(gè)Transition過(guò)渡動(dòng)畫組件”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI