Skip to content

在react中怎么实现组件间的过渡动画?

参考答案:

一、是什么

在日常开发中,页面切换时的转场动画是比较基础的一个场景

当一个组件在显示与消失过程中存在过渡动画,可以很好的增加用户的体验

react中实现过渡动画效果会有很多种选择,如react-transition-groupreact-motionAnimated,以及原生的CSS都能完成切换动画

二、如何实现

react中,react-transition-group是一种很好的解决方案,其为元素添加enterenter-activeexitexit-active这一系列勾子

可以帮助我们方便的实现组件的入场和离场动画

其主要提供了三个主要的组件:

  • CSSTransition:在前端开发中,结合 CSS 来完成过渡动画效果
  • SwitchTransition:两个组件显示和隐藏切换时,使用该组件
  • TransitionGroup:将多个动画组件包裹在其中,一般用于列表中元素的动画

CSSTransition

其实现动画的原理在于,当CSSTransitionin属性置为true时,CSSTransition首先会给其子组件加上xxx-enterxxx-enter-activeclass执行动画

当动画执行结束后,会移除两个class,并且添加-enter-doneclass

所以可以利用这一点,通过csstransition属性,让元素在两个状态之间平滑过渡,从而得到相应的动画效果

in属性置为false时,CSSTransition会给子组件加上xxx-exitxxx-exit-activeclass,然后开始执行动画,当动画结束后,移除两个class,然后添加-enter-doneclass

如下例子:

jsx
export default class App2 extends React.PureComponent {

  state = {show: true};

  onToggle = () => this.setState({show: !this.state.show});

  render() {
    const {show} = this.state;
    return (
      <div className={'container'}>
        <div className={'square-wrapper'}>
          <CSSTransition
            in={show}
            timeout={500}
            classNames={'fade'}
            unmountOnExit={true}
          >
            <div className={'square'} />
          </CSSTransition>
        </div>
        <Button onClick={this.onToggle}>toggle</Button>
      </div>
    );
  }
}

对应css样式如下:

css
.fade-enter {
  opacity: 0;
  transform: translateX(100%);
}

.fade-enter-active {
  opacity: 1;
  transform: translateX(0);
  transition: all 500ms;
}

.fade-exit {
  opacity: 1;
  transform: translateX(0);
}

.fade-exit-active {
  opacity: 0;
  transform: translateX(-100%);
  transition: all 500ms;
}

SwitchTransition

SwitchTransition可以完成两个组件之间切换的炫酷动画

比如有一个按钮需要在onoff之间切换,我们希望看到on先从左侧退出,off再从右侧进入

SwitchTransition中主要有一个属性mode,对应两个值:

  • in-out:表示新组件先进入,旧组件再移除;
  • out-in:表示就组件先移除,新组建再进入

SwitchTransition组件里面要有CSSTransition,不能直接包裹你想要切换的组件

里面的CSSTransition组件不再像以前那样接受in属性来判断元素是何种状态,取而代之的是key属性

下面给出一个按钮入场和出场的示例,如下:

jsx
import { SwitchTransition, CSSTransition } from "react-transition-group";

export default class SwitchAnimation extends PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      isOn: true
    }
  }

  render() {
    const {isOn} = this.state;

    return (
      <SwitchTransition mode="out-in">
        <CSSTransition classNames="btn"
                       timeout={500}
                       key={isOn ? "on" : "off"}>
          {
          <button onClick={this.btnClick.bind(this)}>
            {isOn ? "on": "off"}
          </button>
        }
        </CSSTransition>
      </SwitchTransition>
    )
  }

  btnClick() {
    this.setState({isOn: !this.state.isOn})
  }
}

css文件对应如下:

css
.btn-enter {
  transform: translate(100%, 0);
  opacity: 0;
}

.btn-enter-active {
  transform: translate(0, 0);
  opacity: 1;
  transition: all 500ms;
}

.btn-exit {
  transform: translate(0, 0);
  opacity: 1;
}

.btn-exit-active {
  transform: translate(-100%, 0);
  opacity: 0;
  transition: all 500ms;
}

TransitionGroup

当有一组动画的时候,就可将这些CSSTransition放入到一个TransitionGroup中来完成动画

同样CSSTransition里面没有in属性,用到了key属性

TransitionGroup在感知children发生变化的时候,先保存移除的节点,当动画结束后才真正移除

其处理方式如下:

  • 插入的节点,先渲染dom,然后再做动画

  • 删除的节点,先做动画,然后再删除dom

如下:

jsx
import React, { PureComponent } from 'react'
import { CSSTransition, TransitionGroup } from 'react-transition-group';

export default class GroupAnimation extends PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      friends: []
    }
  }

  render() {
    return (
      <div>
        <TransitionGroup>
          {
            this.state.friends.map((item, index) => {
              return (
                <CSSTransition classNames="friend" timeout={300} key={index}>
                  <div>{item}</div>
                </CSSTransition>
              )
            })
          }
        </TransitionGroup>
        <button onClick={e => this.addFriend()}>+friend</button>
      </div>
    )
  }

  addFriend() {
    this.setState({
      friends: [...this.state.friends, "coderwhy"]
    })
  }
}

对应css如下:

css
.friend-enter {
    transform: translate(100%, 0);
    opacity: 0;
}

.friend-enter-active {
    transform: translate(0, 0);
    opacity: 1;
    transition: all 500ms;
}

.friend-exit {
    transform: translate(0, 0);
    opacity: 1;
}

.friend-exit-active {
    transform: translate(-100%, 0);
    opacity: 0;
    transition: all 500ms;
}

题目要点:

1. 使用 React Transition Group

1.1 介绍 React Transition Group

  • 定义React Transition Group 是 React 官方提供的一个用于处理组件过渡动画的库。它允许你在组件进入、退出或变化时添加过渡动画效果。

  • 安装:你可以通过 npm 或 yarn 安装:

    bash
    npm install react-transition-group
  • 核心组件

    • CSSTransition:为单个元素添加过渡动画。
    • TransitionGroup:管理一组带动画的组件,可以实现多个组件之间的过渡效果。

1.2 使用 CSSTransition 实现过渡动画

  • 基本用法:通过 CSSTransition 包裹需要动画的组件,配置 timeoutclassNames 等属性。classNames 属性定义了动画的 CSS 类前缀,而 timeout 则是动画的持续时间。

  • 示例

    javascript
    import React, { useState } from 'react';
    import { CSSTransition } from 'react-transition-group';
    import './styles.css';
    
    function MyComponent() {
      const [inProp, setInProp] = useState(false);
    
      return (
        <div>
          <CSSTransition in={inProp} timeout={300} classNames="fade">
            <div className="my-component">Hello, world!</div>
          </CSSTransition>
          <button onClick={() => setInProp(!inProp)}>Toggle</button>
        </div>
      );
    }
    
    export default MyComponent;

    CSS

    css
    .fade-enter {
      opacity: 0;
    }
    .fade-enter-active {
      opacity: 1;
      transition: opacity 300ms;
    }
    .fade-exit {
      opacity: 1;
    }
    .fade-exit-active {
      opacity: 0;
      transition: opacity 300ms;
    }

1.3 使用 TransitionGroup 实现组件列表的过渡

  • 基本用法:当你有一组组件需要进行过渡动画时,可以使用 TransitionGroup 来管理这些组件。它与 CSSTransition 结合使用,支持组件的添加、移除动画。

  • 示例

    javascript
    import React, { useState } from 'react';
    import { TransitionGroup, CSSTransition } from 'react-transition-group';
    import './styles.css';
    
    function MyComponent() {
      const [items, setItems] = useState([1, 2, 3]);
    
      const addItem = () => {
        setItems([...items, items.length + 1]);
      };
    
      const removeItem = (index) => {
        setItems(items.filter((_, i) => i !== index));
      };
    
      return (
        <div>
          <button onClick={addItem}>Add Item</button>
          <TransitionGroup>
            {items.map((item, index) => (
              <CSSTransition key={item} timeout={300} classNames="fade">
                <div>
                  Item {item} <button onClick={() => removeItem(index)}>Remove</button>
                </div>
              </CSSTransition>
            ))}
          </TransitionGroup>
        </div>
      );
    }
    
    export default MyComponent;

2. 使用 Framer Motion

2.1 介绍 Framer Motion

  • 定义Framer Motion 是一个强大的动画库,允许你轻松地为 React 组件添加动画效果。它不仅支持简单的过渡动画,还支持复杂的物理效果和路径动画。

  • 安装

    bash
    npm install framer-motion

2.2 使用 Framer Motion 实现过渡动画

  • 基本用法:通过 motion 组件来替代普通的 HTML 元素,并使用 initialanimateexit 属性定义进入、更新和退出时的动画效果。

  • 示例

    javascript
    import React, { useState } from 'react';
    import { motion, AnimatePresence } from 'framer-motion';
    
    function MyComponent() {
      const [isVisible, setIsVisible] = useState(true);
    
      return (
        <div>
          <AnimatePresence>
            {isVisible && (
              <motion.div
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                exit={{ opacity: 0 }}
                transition={{ duration: 0.5 }}
              >
                Hello, world!
              </motion.div>
            )}
          </AnimatePresence>
          <button onClick={() => setIsVisible(!isVisible)}>Toggle</button>
        </div>
      );
    }
    
    export default MyComponent;

3. 使用 CSS 过渡和动画

  • 定义:你也可以直接使用 CSS 的 transitionanimation 属性来实现过渡效果。通过控制组件的 class 切换,来触发相应的 CSS 动画。

  • 示例

    javascript
    function MyComponent() {
      const [isVisible, setIsVisible] = useState(false);
    
      return (
        <div>
          <div className={`box ${isVisible ? 'visible' : ''}`}>Hello, world!</div>
          <button onClick={() => setIsVisible(!isVisible)}>Toggle</button>
        </div>
      );
    }

    CSS

    css
    .box {
      opacity: 0;
      transition: opacity 0.5s ease-in-out;
    }
    .box.visible {
      opacity: 1;
    }

4. 总结与注意事项

  • 选择适合的动画库或方法:根据项目的需求和复杂度,可以选择 React Transition GroupFramer Motion 或纯 CSS 进行动画实现。
  • 性能优化:尽量减少不必要的重绘和重排,使用 requestAnimationFrame 进行复杂动画优化。