一個簡單的構建React元件動畫方案

advence-liz發表於2019-04-11

最近對前端動畫方案做了一些調研,想找到一個簡單且易複製的方案在React元件中使用(也就是用複製貼上解決80%的問題),最終選擇官方的react-transition-groupanimate.css

線上DEMO

傳送門點開看看是不是你想要的樣子

我調研過的方案

  • anijs online demo 酷炫是很酷炫但是引用方式比較原始不太適合現在的前端構建流程和React
  • velocity.js 用jQuery $.animate() 相同的API 但是可以不依賴 jQuery,這方式並不是我所期望的故放棄
  • react-motion 非常酷炫如果要做複雜動畫目前來看非它莫屬,但是於我簡單易複製的期望不符
  • animate.css 用css 實現了各種常見的動畫效果,而且還有人封裝了react-animated-css
  • react-transition-group 官方的方案容易上手,但是應對複雜動畫比較無力,可是我並不做複雜的動畫因此這也就是心中的完美方案

關於CSS動畫

本文並不打算介紹 CSS 動畫但是推薦一些資源,如果你對CSS 動畫比較陌生也先閱讀下面的資源

animate.css

animate.css 是一個出色的樣式庫,提供了各種常用的CSS 動畫效果,簡易的例子如下基本見名知意這裡就不做額外的解釋了

<div class="animated bounce delay-2s">Example</div>
複製程式碼

不過大多數的時候我們必然不需要引入全部樣式,甚至我們可能只想copy一個動畫效果,在這裡我fork 了一份 animate.css 然後在其構建的過程中新增了sourcemap方便copy

帶sourcemap的DEMO站點開啟控制檯開啟複製貼上之旅

an

簡單介紹一下react-transition-group 中的 CSSTransition

CSSTransiotn 會在動畫的生命週期內為其指定的子元素新增代表其處於指定生命週期的class 假設有如下DEMO 當 CSSTransitionin屬性值切換時true的時候會依次給chidern 新增 fade-enter, fade-enter-active, fade-enter-done。 當 CSSTransitionin屬性值切換時false的時候會依次給chidern 新增 fade-exit, fade-exit-active, fade-exit-done。

其中 -enter-active緊隨 -enter之後新增並且跟-enter 同時存在,而-enter-done在動畫結束時新增並且與-enter-activeenter互斥,exit同理。

所以當我們要利用CSSTransition實現動畫效果的時候,只需要定義出對應時間點出現的class樣式即可,需要注意的倆點

  • 動畫結束的時間根據timeout決定所以所寫的樣式during必須跟與其對應(之後我們會對CSSTransition進行簡單封裝解決這個問題)
  • CSSTransition決定字首的引數是classNames 不是className
    <CSSTransition
            in={fadeIn}
            timeout={2000}
            unmountOnExit
            classNames="fade"
            onEnter={this.onEnter}
            onEntered={this.onEntered}
            onExit={this.onExit}
            onExited={this.onExited}
          >
        <div className="demo" >fade-{fadeIn ? 'in' : 'out'}</div>
    </CSSTransition>
複製程式碼

雖然在動畫的執行的生命週期內出現了6個關鍵點但是使用css3 animation實現動畫效果時我們只需操作倆個時間點 -enter-exit就ok了,之後要做的就是在animate.csscopy 對應的程式碼

@keyframes fadeIn {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

@keyframes fadeOut {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}
.fade {
  &-enter {
    animation-name: fadeIn;
    animation-duration: 2000ms;
  }
  &-exit {
    animation-name: fadeOut;
    animation-duration: 2000ms;
  }
}
複製程式碼

對CSSTransition 進行簡單封裝

利用React.cloneElement對為CSSTransition 預設新增 animated class,並且通過設定內聯的樣式的方式讓動畫效果結束的時間跟timeout欄位一致

animated為了為動畫設定一些預設的樣式,比如像下面這樣預設設定動畫時長為1s animation-fill-modeboth

.animated {
  animation-duration: 1s;
  animation-fill-mode: both;
}
@media (print), (prefers-reduced-motion) {
  .animated {
    animation: unset !important;
    transition: none !important;
  }
}

複製程式碼

封裝程式碼示意

import React from 'react'
import { CSSTransition } from 'react-transition-group'

let count = 0
export default class Animation extends React.Component {
  static defaultProps = {
    in: true,
    timeout: 1000,// 與 .animate 中設定的預設時間對應
    unmountOnExit: true,
    classNames: '',
    onEnter () {},
    onEntered () {},
    onExit () {},
    onExited () {}
  }
  constructor () {
    super()
    this.count = count++
  }
  onEnter = () => {
    console.time(`enter${this.count}`)
    this.props.onEnter()
  }
  onEntered = () => {
    console.timeEnd(`enter${this.count}`)
    this.props.onEntered()
  }
  onExit = () => {
    console.time(`exit${this.count}`)
    this.props.onExit()
  }
  onExited = () => {
    console.timeEnd(`exit${this.count}`)
    this.props.onExited()
  }

  render () {
    const {
      in: isIn,
      timeout,
      unmountOnExit,
      classNames,
      children
    } = this.props
    const {
      props: { className = '', style = {} }
    } = children

    return (
      <CSSTransition
        in={isIn}
        timeout={timeout}
        unmountOnExit={unmountOnExit}
        classNames={classNames}
        onEnter={this.onEnter}
        onEntered={this.onEntered}
        onExit={this.onExit}
        onExited={this.onExited}
      >
        {React.cloneElement(children, {
          className: `animated ${className}`,
          style: {
            ...{
              '--webkit-animation-duration': `${timeout}ms`,
              animationDuration: `${timeout}ms`
            },
            ...style
          }
        })}
      </CSSTransition>
    )
  }
}
複製程式碼

相關文章