133

这么简单的事情应该很容易完成,但我正在为它的复杂性而烦恼。

我要做的就是为 React 组件的安装和卸载设置动画,仅此而已。这是我迄今为止尝试过的以及为什么每个解决方案都不起作用的原因:

  1. ReactCSSTransitionGroup- 我根本没有使用 CSS 类,都是 JS 样式,所以这行不通。
  2. ReactTransitionGroup- 这个较低级别的 API 很棒,但它需要您在动画完成时使用回调,因此在这里仅使用 CSS 过渡将不起作用。总是有动画库,这就引出了下一点:
  3. GreenSock - 许可对于商业用途 IMO 来说过于严格。
  4. React Motion - 这看起来很棒,但是TransitionMotion对于我需要的东西来说非常混乱和过于复杂。
  5. 当然,我可以像 Material UI 那样做一些诡计,其中元素被渲染但保持隐藏(left: -10000px)但我宁愿不走那条路。我认为它很hacky,我希望我的组件卸载,以便它们清理并且不会弄乱DOM。

我想要一些易于实现的东西。在安装时,为一组样式设置动画;在卸载时,为相同(或另一组)样式设置动画。完毕。它还必须在多个平台上具有高性能。

我在这里撞到了一堵砖墙。如果我遗漏了什么并且有一种简单的方法可以做到这一点,请告诉我。

4

19 回答 19

122

这有点冗长,但我已经使用了所有本机事件和方法来实现这个动画。不ReactCSSTransitionGroupReactTransitionGroup等等。

我用过的东西

  • 反应生命周期方法
  • onTransitionEnd事件

这是如何工作的

  • 根据 mount 属性传递(mounted)和默认样式(opacity: 0)安装元素
  • 挂载或更新后,使用componentDidMount(componentWillReceiveProps用于进一步更新) 更改样式 ( opacity: 1) 并超时(使其异步)。
  • 在卸载期间,将一个道具传递给组件以识别卸载,再次更改样式(opacity: 0),,onTransitionEnd从 DOM 中删除卸载元素。

继续循环。

看一遍代码,你就明白了。如果需要任何澄清,请发表评论。

希望这可以帮助。

class App extends React.Component{
  constructor(props) {
    super(props)
    this.transitionEnd = this.transitionEnd.bind(this)
    this.mountStyle = this.mountStyle.bind(this)
    this.unMountStyle = this.unMountStyle.bind(this)
    this.state ={ //base css
      show: true,
      style :{
        fontSize: 60,
        opacity: 0,
        transition: 'all 2s ease',
      }
    }
  }
  
  componentWillReceiveProps(newProps) { // check for the mounted props
    if(!newProps.mounted)
      return this.unMountStyle() // call outro animation when mounted prop is false
    this.setState({ // remount the node when the mounted prop is true
      show: true
    })
    setTimeout(this.mountStyle, 10) // call the into animation
  }
  
  unMountStyle() { // css for unmount animation
    this.setState({
      style: {
        fontSize: 60,
        opacity: 0,
        transition: 'all 1s ease',
      }
    })
  }
  
  mountStyle() { // css for mount animation
    this.setState({
      style: {
        fontSize: 60,
        opacity: 1,
        transition: 'all 1s ease',
      }
    })
  }
  
  componentDidMount(){
    setTimeout(this.mountStyle, 10) // call the into animation
  }
  
  transitionEnd(){
    if(!this.props.mounted){ // remove the node on transition end when the mounted prop is false
      this.setState({
        show: false
      })
    }
  }
  
  render() {
    return this.state.show && <h1 style={this.state.style} onTransitionEnd={this.transitionEnd}>Hello</h1> 
  }
}

class Parent extends React.Component{
  constructor(props){
    super(props)
    this.buttonClick = this.buttonClick.bind(this)
    this.state = {
      showChild: true,
    }
  }
  buttonClick(){
    this.setState({
      showChild: !this.state.showChild
    })
  }
  render(){
    return <div>
        <App onTransitionEnd={this.transitionEnd} mounted={this.state.showChild}/>
        <button onClick={this.buttonClick}>{this.state.showChild ? 'Unmount': 'Mount'}</button>
      </div>
  }
}

ReactDOM.render(<Parent />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-with-addons.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

于 2016-10-21T08:49:29.940 回答
37

这是我使用新的钩子 API(使用 TypeScript)的解决方案,基于这篇文章,用于延迟组件的卸载阶段:

function useDelayUnmount(isMounted: boolean, delayTime: number) {
    const [ shouldRender, setShouldRender ] = useState(false);

    useEffect(() => {
        let timeoutId: number;
        if (isMounted && !shouldRender) {
            setShouldRender(true);
        }
        else if(!isMounted && shouldRender) {
            timeoutId = setTimeout(
                () => setShouldRender(false), 
                delayTime
            );
        }
        return () => clearTimeout(timeoutId);
    }, [isMounted, delayTime, shouldRender]);
    return shouldRender;
}

用法:

const Parent: React.FC = () => {
    const [ isMounted, setIsMounted ] = useState(true);
    const shouldRenderChild = useDelayUnmount(isMounted, 500);
    const mountedStyle = {opacity: 1, transition: "opacity 500ms ease-in"};
    const unmountedStyle = {opacity: 0, transition: "opacity 500ms ease-in"};

    const handleToggleClicked = () => {
        setIsMounted(!isMounted);
    }

    return (
        <>
            {shouldRenderChild && 
                <Child style={isMounted ? mountedStyle : unmountedStyle} />}
            <button onClick={handleToggleClicked}>Click me!</button>
        </>
    );
}

代码沙盒链接。

于 2019-01-09T16:12:40.260 回答
16

利用从 Pranesh 的回答中获得的知识,我想出了一个可配置和可重用的替代解决方案:

const AnimatedMount = ({ unmountedStyle, mountedStyle }) => {
  return (Wrapped) => class extends Component {
    constructor(props) {
      super(props);
      this.state = {
        style: unmountedStyle,
      };
    }

    componentWillEnter(callback) {
      this.onTransitionEnd = callback;
      setTimeout(() => {
        this.setState({
          style: mountedStyle,
        });
      }, 20);
    }

    componentWillLeave(callback) {
      this.onTransitionEnd = callback;
      this.setState({
        style: unmountedStyle,
      });
    }

    render() {
      return <div
        style={this.state.style}
        onTransitionEnd={this.onTransitionEnd}
      >
        <Wrapped { ...this.props } />
      </div>
    }
  }
};

用法:

import React, { PureComponent } from 'react';

class Thing extends PureComponent {
  render() {
    return <div>
      Test!
    </div>
  }
}

export default AnimatedMount({
  unmountedStyle: {
    opacity: 0,
    transform: 'translate3d(-100px, 0, 0)',
    transition: 'opacity 250ms ease-out, transform 250ms ease-out',
  },
  mountedStyle: {
    opacity: 1,
    transform: 'translate3d(0, 0, 0)',
    transition: 'opacity 1.5s ease-out, transform 1.5s ease-out',
  },
})(Thing);

最后,在另一个组件的render方法中:

return <div>
  <ReactTransitionGroup>
    <Thing />
  </ReactTransitionGroup>
</div>
于 2016-11-08T23:10:30.367 回答
13

我在工作中反驳了这个问题,看起来很简单,它实际上不在 React 中。在正常情况下,您呈现如下内容:

this.state.show ? {childen} : null;

随着this.state.show变化,孩子们立即安装/卸载。

我采用的一种方法是创建一个包装组件Animate并像使用它一样使用它

<Animate show={this.state.show}>
  {childen}
</Animate>

现在随着this.state.show变化,我们可以感知道具变化getDerivedStateFromProps(componentWillReceiveProps)并创​​建中间渲染阶段来执行动画。

一个阶段循环可能看起来像这样

当孩子被安装或卸载时,我们从静态阶段开始。

一旦我们检测到show标志的变化,我们就进入准备阶段,在这里我们计算必要的属性,比如heightwidthfrom ReactDOM.findDOMNode.getBoundingClientRect()

然后进入Animate State,我们可以使用 css 过渡将高度、宽度和不透明度从 0 更改为计算值(如果卸载,则更改为 0)。

在过渡结束时,我们使用onTransitionEndapi 切换回 Static阶段。

关于阶段如何顺利转移还有更多细节,但这可能是总体思路:)

如果有人感兴趣,我创建了一个 React 库https://github.com/MingruiZhang/react-animate-mount来分享我的解决方案。欢迎任何反馈:)

于 2018-07-12T23:27:27.817 回答
8

我认为使用Transitionfromreact-transition-group可能是跟踪安装/卸载的最简单方法。它非常灵活。我正在使用一些类来展示它的易用性,但你绝对可以使用addEndListenerprop 连接你自己的 JS 动画——我也很幸运地使用了 GSAP。

沙盒:https ://codesandbox.io/s/k9xl9mkx2o

这是我的代码。

import React, { useState } from "react";
import ReactDOM from "react-dom";
import { Transition } from "react-transition-group";
import styled from "styled-components";

const H1 = styled.h1`
  transition: 0.2s;
  /* Hidden init state */
  opacity: 0;
  transform: translateY(-10px);
  &.enter,
  &.entered {
    /* Animate in state */
    opacity: 1;
    transform: translateY(0px);
  }
  &.exit,
  &.exited {
    /* Animate out state */
    opacity: 0;
    transform: translateY(-10px);
  }
`;

const App = () => {
  const [show, changeShow] = useState(false);
  const onClick = () => {
    changeShow(prev => {
      return !prev;
    });
  };
  return (
    <div>
      <button onClick={onClick}>{show ? "Hide" : "Show"}</button>
      <Transition mountOnEnter unmountOnExit timeout={200} in={show}>
        {state => {
          let className = state;
          return <H1 className={className}>Animate me</H1>;
        }}
      </Transition>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
于 2019-03-02T02:51:18.953 回答
7

成帧器运动

从 npm 安装 framer-motion。

import { motion, AnimatePresence } from "framer-motion"

export const MyComponent = ({ isVisible }) => (
  <AnimatePresence>
    {isVisible && (
      <motion.div
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
      />
    )}
  </AnimatePresence>
)
于 2020-01-15T16:27:58.987 回答
2

对于那些考虑 react-motion 的人来说,在安装和卸载单个组件时对其进行动画设置可能会让人不知所措。

有一个名为react-motion-ui-pack的库,它使这个过程更容易开始。它是 react-motion 的包装器,这意味着您可以从库中获得所有好处(即您可以中断动画,同时发生多个卸载)。

用法:

import Transition from 'react-motion-ui-pack'

<Transition
  enter={{ opacity: 1, translateX: 0 }}
  leave={{ opacity: 0, translateX: -100 }}
  component={false}
>
  { this.state.show &&
      <div key="hello">
        Hello
      </div>
  }
</Transition>

Enter 定义了组件的最终状态应该是什么;leave 是卸载组件时应用的样式。

您可能会发现,一旦您使用了几次 UI 包,react-motion 库可能就不再那么令人生畏了。

于 2017-03-30T11:14:52.917 回答
2

你可以使用React Transition Group来做到这一点。它为您提供 CSS 类,因此您可以在这些 CSS 类中编写动画代码。

按照这个简单的例子

import {CSSTransition } from 'react-transition-group';//This should be imported
import './AnimatedText.css';

const AnimatedText = () => {
    const [showText, setShowText] = useState(false); //By default text will be not shown

    //Handler to switch states
    const switchHandler = () =>{
        setShowText(!showText);
    };

    return (
        //in : pass your state here, it will used by library to toggle. It should be boolean
        //timeout: your amination total time(it should be same as mentioned in css)
        //classNames: give class name of your choice, library will prefix it with it's animation classes
        //unmountOnExit: Component will be unmounted when your state changes to false
        <CSSTransition in={showText} timeout={500} classNames='fade' unmountOnExit={true}>
            <h1>Animated Text</h1>
        </CSSTransition>  
        <button onClick={switchHandler}>Show Text</button>                  
    );
};

export default AnimatedText;

现在,让我们在 CSS 文件(AnimatedText.css)中编写动画,记住 classNames 属性(在本例中为 fade)

//fade class should be prefixed

/*****Fade In effect when component is mounted*****/
//This is when your animation starts
fade-enter {
  opacity: 0;
}

//When your animation is active
.fade-enter.fade-enter-active {
  opacity: 1;
  transition: all 500ms ease-in;
}
/*****Fade In effect when component is mounted*****/


/*****Fade Out effect when component is unmounted*****/
.fade-exit {
  opacity: 1;
}
.fade-exit-active {
  opacity: 0;
  transition: all 500ms ease-out;
}

/*****Fade Out effect when component is unmounted*****/

还有一个出现类,可以在您的组件第一次加载时使用。查看文档以获取更多详细信息

于 2021-01-21T10:09:06.680 回答
1

使用react-move动画进入和退出过渡要容易得多。

代码沙盒上的示例

于 2017-10-31T19:30:20.373 回答
0

这是我的 2cents:感谢@deckele 的解决方案。我的解决方案是基于他的,它是有状态的组件版本,完全可重用。

这是我的沙箱:https ://codesandbox.io/s/302mkm1m 。

这是我的片段.js:

import ReactDOM from "react-dom";
import React, { Component } from "react";
import style from  "./styles.css"; 

class Tooltip extends Component {

  state = {
    shouldRender: false,
    isMounted: true,
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.state.shouldRender !== nextState.shouldRender) {
      return true
    }
    else if (this.state.isMounted !== nextState.isMounted) {
      console.log("ismounted!")
      return true
    }
    return false
  }
  displayTooltip = () => {
    var timeoutId;
    if (this.state.isMounted && !this.state.shouldRender) {
      this.setState({ shouldRender: true });
    } else if (!this.state.isMounted && this.state.shouldRender) {
      timeoutId = setTimeout(() => this.setState({ shouldRender: false }), 500);
      () => clearTimeout(timeoutId)
    }
    return;
  }
  mountedStyle = { animation: "inAnimation 500ms ease-in" };
  unmountedStyle = { animation: "outAnimation 510ms ease-in" };

  handleToggleClicked = () => {
    console.log("in handleToggleClicked")
    this.setState((currentState) => ({
      isMounted: !currentState.isMounted
    }), this.displayTooltip());
  };

  render() {
    var { children } = this.props
    return (
      <main>
        {this.state.shouldRender && (
          <div className={style.tooltip_wrapper} >
            <h1 style={!(this.state.isMounted) ? this.mountedStyle : this.unmountedStyle}>{children}</h1>
          </div>
        )}

        <style>{`

           @keyframes inAnimation {
    0% {
      transform: scale(0.1);
      opacity: 0;
    }
    60% {
      transform: scale(1.2);
      opacity: 1;
    }
    100% {
      transform: scale(1);  
    }
  }

  @keyframes outAnimation {
    20% {
      transform: scale(1.2);
    }
    100% {
      transform: scale(0);
      opacity: 0;
    }
  }
          `}
        </style>
      </main>
    );
  }
}


class App extends Component{

  render(){
  return (
    <div className="App"> 
      <button onClick={() => this.refs.tooltipWrapper.handleToggleClicked()}>
        click here </button>
      <Tooltip
        ref="tooltipWrapper"
      >
        Here a children
      </Tooltip>
    </div>
  )};
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
于 2019-02-21T23:34:32.003 回答
0

这是我在 2019 年解决这个问题的方法,同时制作了一个加载微调器。我正在使用 React 功能组件。

我有一个父App组件,它有一个子Spinner组件。

应用程序具有应用程序是否正在加载的状态。应用加载时,Spinner正常渲染。当应用程序未加载时(isLoading为 false)Spinner使用 prop 渲染shouldUnmount

应用程序.js

import React, {useState} from 'react';
import Spinner from './Spinner';

const App = function() {
    const [isLoading, setIsLoading] = useState(false);

    return (
        <div className='App'>
            {isLoading ? <Spinner /> : <Spinner shouldUnmount />}
        </div>
    );
};

export default App;

Spinner具有是否隐藏的状态。一开始,使用默认的 props 和 state,Spinner是正常渲染的。类Spinner-fadeIn动画它淡入。当SpinnershouldUnmount接收到它与类一起渲染的道具时Spinner-fadeOut,动画它淡出。

但是,我还希望组件在淡出后卸载。

此时我尝试使用onAnimationEndReact 合成事件,类似于上面@pranesh-ravi 的解决方案,但它不起作用。相反,我曾经setTimeout将状态设置为隐藏,延迟与动画长度相同。Spinner将在延迟后更新isHidden === true,并且不会渲染任何内容。

这里的关键是父级并没有卸载子级,它告诉子级何时卸载,子级在处理完自己的卸载业务后自行卸载。

微调器.js

import React, {useState} from 'react';
import './Spinner.css';

const Spinner = function(props) {
    const [isHidden, setIsHidden] = useState(false);

    if(isHidden) {
        return null

    } else if(props.shouldUnmount) {
        setTimeout(setIsHidden, 500, true);
        return (
            <div className='Spinner Spinner-fadeOut' />
        );

    } else {
        return (
            <div className='Spinner Spinner-fadeIn' />
        );
    }
};

export default Spinner;

微调器.css:

.Spinner {
    position: fixed;
    display: block;
    z-index: 999;
    top: 50%;
    left: 50%;
    margin: -40px 0 0 -20px;
    height: 40px;
    width: 40px;
    border: 5px solid #00000080;
    border-left-color: #bbbbbbbb;
    border-radius: 40px;
}

.Spinner-fadeIn {
    animation: 
        rotate 1s linear infinite,
        fadeIn .5s linear forwards;
}

.Spinner-fadeOut {
    animation: 
        rotate 1s linear infinite,
        fadeOut .5s linear forwards;
}

@keyframes fadeIn {
    0% {
        opacity: 0;
    }
    100% {
        opacity: 1;
    }
}
@keyframes fadeOut {
    0% {
        opacity: 1;
    }
    100% {
        opacity: 0;
    }
}

@keyframes rotate {
    100% {
        transform: rotate(360deg);
    }
}
于 2019-03-12T02:22:06.210 回答
0

我也急需单组件 Animation 。我厌倦了使用 React Motion,但我为这样一个微不足道的问题而烦恼..(我喜欢)。经过一番谷歌搜索后,我在他们的 git repo 上看到了这篇文章。希望它可以帮助某人..

引用自&也是信用。到目前为止,这对我有用。我的用例是在加载和卸载的情况下动画和卸载的模式。

class Example extends React.Component {
  constructor() {
    super();
    
    this.toggle = this.toggle.bind(this);
    this.onRest = this.onRest.bind(this);

    this.state = {
      open: true,
      animating: false,
    };
  }
  
  toggle() {
    this.setState({
      open: !this.state.open,
      animating: true,
    });
  }
  
  onRest() {
    this.setState({ animating: false });
  }
  
  render() {
    const { open, animating } = this.state;
    
    return (
      <div>
        <button onClick={this.toggle}>
          Toggle
        </button>
        
        {(open || animating) && (
          <Motion
            defaultStyle={open ? { opacity: 0 } : { opacity: 1 }}
            style={open ? { opacity: spring(1) } : { opacity: spring(0) }}
            onRest={this.onRest}
          >
            {(style => (
              <div className="box" style={style} />
            ))}
          </Motion>
        )}
      </div>
    );
  }
}

于 2019-06-05T19:13:05.753 回答
0

这可以使用CSSTransition来自的组件轻松完成react-transition-group,就像您提到的库一样。诀窍是您需要在没有显示/隐藏机制的情况下包装 CSSTransition 组件,就像通常那样{show && <Child>}...。否则,您将隐藏动画并且它将无法工作。例子:

ParentComponent.js

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

function ParentComponent({show}) {
return (
  <CSSTransition classes="parentComponent-child" in={show} timeout={700}>
    <ChildComponent>
  </CSSTransition>
)}


ParentComponent.css

// animate in
.parentComponent-child-enter {
  opacity: 0;
}
.parentComponent-child-enter-active {
  opacity: 1;
  transition: opacity 700ms ease-in;
}
// animate out
.parentComponent-child-exit {
  opacity: 1;
}
.parentComponent-child-exit-active {
  opacity: 0;
  transition: opacity 700ms ease-in;
}
于 2019-09-13T22:21:13.967 回答
0

我知道这里有很多答案,但我仍然没有找到适合我需要的答案。我想:

  • 功能组件
  • 一种解决方案,可让我的组件在安装/卸载时轻松淡入/淡出。

经过几个小时的摆弄,我有一个可行的解决方案,我会说 90%。我在下面的代码中的注释块中写了限制。我仍然喜欢更好的解决方案,但这是我找到的最好的解决方案,包括这里的其他解决方案。

const TIMEOUT_DURATION = 80 // Just looked like best balance of silky smooth and stop delaying me.

// Wrap this around any views and they'll fade in and out when mounting /
// unmounting.  I tried using <ReactCSSTransitionGroup> and <Transition> but I
// could not get them to work.  There is one major limitation to this approach:
// If a component that's mounted inside of <Fade> has direct prop changes,
// <Fade> will think that it's a new component and unmount/mount it.  This
// means the inner component will fade out and fade in, and things like cursor
// position in forms will be reset. The solution to this is to abstract <Fade>
// into a wrapper component.

const Fade: React.FC<{}> = ({ children }) => {
  const [ className, setClassName ] = useState('fade')
  const [ newChildren, setNewChildren ] = useState(children)

  const effectDependency = Array.isArray(children) ? children : [children]

  useEffect(() => {
    setClassName('fade')

    const timerId = setTimeout(() => {
      setClassName('fade show')
      setNewChildren(children)
    }, TIMEOUT_DURATION)

    return () => {
      clearTimeout(timerId)
    }   

  }, effectDependency)

  return <Container fluid className={className + ' p-0'}>{newChildren}</Container>
}

如果您有要淡入/淡出的组件,请将其包装在<Fade>Ex. <Fade><MyComponent/><Fade>.

请注意,这react-bootstrap用于类名和 for <Container/>,但两者都可以很容易地替换为自定义 CSS 和常规旧的<div>.

于 2020-04-10T22:32:25.680 回答
0

如果我使用VelocityorAnimeJS库直接为节点设置动画(而不是cssor setTimeout),那么我发现我可以设计一个hook来提供动画状态on和功能onToggle来启动动画(例如向下滑动、淡入淡出)。

基本上钩子的作用是打开和关闭动画,然后相应地更新on。因此我们可以准确地获取动画的状态。如果不这样做,将回复 ad-hoc duration

/**
 * A hook to provide animation status.
 * @class useAnimate
 * @param {object} _                props
 * @param {async} _.animate         Promise to perform animation
 * @param {object} _.node           Dom node to animate
 * @param {bool} _.disabled         Disable animation
 * @returns {useAnimateObject}      Animate status object
 * @example
 *   const { on, onToggle } = useAnimate({
 *    animate: async () => { },
 *    node: node
 *  })
 */

import { useState, useCallback } from 'react'

const useAnimate = ({
  animate, node, disabled,
}) => {
  const [on, setOn] = useState(false)

  const onToggle = useCallback(v => {
    if (disabled) return
    if (v) setOn(true)
    animate({ node, on: v }).finally(() => {
      if (!v) setOn(false)
    })
  }, [animate, node, disabled, effect])

  return [on, onToggle]
}

export default useAnimate

用法如下,

  const ref = useRef()
  const [on, onToggle] = useAnimate({
    animate: animateFunc,
    node: ref.current,
    disabled
  })
  const onClick = () => { onToggle(!on) }

  return (
      <div ref={ref}>
          {on && <YOUROWNCOMPONENT onClick={onClick} /> }
      </div>
  )

动画实现可能是,

import anime from 'animejs'

const animateFunc = (params) => {
  const { node, on } = params
  const height = on ? 233 : 0
  return new Promise(resolve => {
    anime({
      targets: node,
      height,
      complete: () => { resolve() }
    }).play()
  })
}

于 2020-05-24T02:31:31.320 回答
0

您可以为此使用React SyntheticEvent

通过 onAnimationEnd或onTransitionEnd之类的事件,您可以做到这一点。

反应文档:https ://reactjs.org/docs/events.html#animation-events

代码示例:https ://dev.to/michalczaplinski/super-easy-react-mount-unmount-animations-with-hooks-4foj

于 2020-09-04T21:24:18.797 回答
0

你总是可以使用 React 生命周期方法,但 react-transition-group 是迄今为止我遇到的最方便的动画库,无论你使用的是styled-componentsCSS 还是纯 CSS。当您想要跟踪组件的安装和卸载并相应地渲染动画时,它特别有用。Transition与 styled-components 一起使用,以及CSSTransition在使用纯 css 类名时使用。

于 2020-11-10T07:15:18.940 回答
0

如果您正在寻找简单的钩子示例:

import React, { useEffect, useReducer } from "react";
import ReactDOM from "react-dom";

const ANIMATION_TIME = 2 * 1000;

function Component() {
  const [isMounted, toggleMounted] = useReducer((p) => !p, true);
  const [isAnimateAnmount, toggleAnimateUnmount] = useReducer((p) => !p, false);
  const [isVisible, toggleVisible] = useReducer((p) => (p ? 0 : 1), 0);

  useEffect(() => {
    if (isAnimateAnmount) {
      toggleVisible();
      toggleAnimateUnmount();
      setTimeout(() => {
        toggleMounted();
      }, ANIMATION_TIME);
    }
  }, [isAnimateAnmount]);

  useEffect(() => {
    toggleVisible();
  }, [isMounted]);

  return (
    <>
      <button onClick={toggleAnimateUnmount}>toggle</button>
      <div>{isMounted ? "Mounted" : "Unmounted"}</div>
      {isMounted && (
        <div
          style={{
            fontSize: 60,
            opacity: isVisible,
            transition: "all 2s ease"
          }}
        >
          Example
        </div>
      )}
    </>
  );
}

使用卸载编辑动画

于 2021-01-26T13:25:23.830 回答
0

我创建了一个名为 WrapperComponent 的通用组件MountAnimation,这样您就可以对元素进行动画处理,而不必总是一遍又一遍地编写相同的东西。它在后台使用CSSTransitions,因此您需要安装它。

  1. 安装依赖
npm install react-transition-group
  1. 在您的文件夹之一中创建组件
import { CSSTransition } from "react-transition-group"

export const MountAnimation = ({
  children,
  timeout = 300, // MATCH YOUR DEFAULT ANIMATION DURATION
  isVisible = false,
  unmountOnExit = true,
  classNames = "transition-translate-y", // ADD YOUR DEFAULT ANIMATION
  ...restProps
}) => {
  return (
    <CSSTransition
      in={isVisible}
      timeout={timeout}
      classNames={classNames}
      unmountOnExit={unmountOnExit}
      {...restProps}
    >
      <div>{children}</div>
    </CSSTransition>
  )
}
  1. 只需像这样使用它:
import { MountAnimation } from '../../path/to/component'

...

const [isElementVisible, setIsElementVisible] = useState(false)

return (
    <MountAnimation isVisible={isElementVisible}>
       // your content here
    </MountAnimation>

)
  1. (在这里发挥创意)您需要在 CSS 文件中声明动画。如果要进行代码拆分,请确保在全局可用的 CSS 文件中声明这一点。在此示例中,我使用以下动画:
.transition-translate-y-enter {
  opacity: 0;
  transform: translateY(-5px);
}
.transition-translate-y-enter-active {
  opacity: 1;
  transform: translateY(0px);
  transition: opacity 300ms ease-in-out, transform 300ms ease-in-out;
}
.transition-translate-y-exit {
  opacity: 1;
  transform: translateY(0px);
}
.transition-translate-y-exit-active {
  opacity: 0;
  transform: translateY(-5px);
  transition: opacity 300ms ease-in-out, transform 300ms ease-in-out;
}

下面是这个实现的一个活生生的例子:

https://codesandbox.io/s/vibrant-elion-ngfzr?file=/src/App.js

于 2022-01-26T11:53:26.473 回答