动画-React-动画化单个组件的安装和卸载

这个简单的事情应该很容易实现,但是我要把它弄复杂。

我要做的就是动画化React组件的安装和卸载。 到目前为止,这是我已经尝试过的方法,以及每种解决方案都行不通的原因:

  1. left: -10000px-我根本不使用CSS类,因为它们都是JS样式,所以这行不通。
  2. left: -10000px-这个较低级别的API很不错,但是它要求您在动画制作完成后使用回调,因此仅使用CSS过渡在此处无法正常工作。 总是有动画库,这引出了下一点:
  3. GreenSock-许可证对于IMO的商业用途过于严格。
  4. React Motion-看起来很棒,但是left: -10000px对于我所需要的东西非常混乱且过于复杂。
  5. 当然,我可以像Material UI一样做一些技巧,在其中呈现元素但保持隐藏状态(left: -10000px),但我宁愿不走这条路。 我认为它很笨拙,并且我希望卸下我的组件,以便它们清理并不会弄乱DOM。

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

我在这里撞墙了。 如果我缺少某些东西,并且有一种简单的方法可以做到,请告诉我。

10个解决方案
75 votes

这有点冗长,但是我使用了所有本机事件和方法来实现此动画。 <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>onTransitionEnd等。

我用过的东西

  • 反应生命周期方法
  • <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>事件

如何运作

  • 根据已通过的安装道具(<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>)和默认样式(onTransitionEnd)来安装元素
  • 挂载或更新后,使用<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>onTransitionEnd以进行进一步的更新)更改样式(opacity: 1)并设置超时(以使其异步)。
  • 在卸载过程中,将prop传递给组件以标识要卸载,然后再次更改样式(<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>onTransitionEnd,从DOM中删除要卸载的元素。

继续循环。

查看代码,您会明白的。 如果需要任何澄清,请发表评论。

希望这可以帮助。

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

Pranesh Ravi answered 2019-10-06T02:07:25Z
14 votes

利用从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>
ffxsam answered 2019-10-06T02:07:58Z
7 votes

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

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

随着onTransitionEnd的更改,将立即安装/卸载孩子。

我采用的一种方法是创建包装器组件onTransitionEnd,并像

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

现在随着onTransitionEnd的更改,我们可以使用Static感知道具更改,并创建中间渲染阶段来执行动画。

A stage cycle might look like this

我们从安装或卸载子代的静态舞台开始。

一旦我们检测到onTransitionEnd的标志变化,便进入准备阶段,在该阶段我们计算ReactDOM.findDOMNode.getBoundingClientRect()的必要属性,如Staticwidth

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

在过渡结束时,我们使用onTransitionEnd API将其更改回  Static阶段。

有关阶段如何平稳转移的更多细节,但这可能是一个整体想法:)

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

Mingrui Zhang answered 2019-10-06T02:09:27Z
2 votes

这是基于此帖子的使用新的hooks API(带有TypeScript)的解决方案,用于延迟组件的卸载阶段:

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

    useEffect(() => {
        let timeoutId: NodeJS.Timeout;
        if (isMounted && !shouldRender) {
            setShouldRender(true);
        }
        else if(!isMounted && shouldRender) {
            timeoutId = setTimeout(
                () => setShouldRender(false), 
                delayTime
            );
        }
        return () => clearTimeout(timeoutId);
    });
    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>
        </>
    );
}

CodeSandbox链接。

deckele answered 2019-10-06T02:10:01Z
2 votes

我认为使用react-transition-group中的Transition可能是跟踪安装/卸载的最简单方法。 它非常灵活。 我正在使用一些类来演示它的易用性,但是您绝对可以使用addEndListener prop来连接自己的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);
Shalanah answered 2019-10-06T02:10:41Z
1 votes

如果在onMount上添加另一个具有过渡的className,而在onUnMount上删除该类名怎么办?

Frazer Kirkman answered 2019-10-06T02:11:07Z
1 votes

对于那些考虑反作用运动的人,在安装和卸载单个组件时设置动画效果可能会很困难。

有一个名为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定义组件的最终状态。 离开是在卸载组件时应用的样式。

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

Björn Holdt answered 2019-10-06T02:11:56Z
1 votes

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

codeandbox上的示例

delimited answered 2019-10-06T02:12:28Z
0 votes

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

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

这是我的snippet.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);
Webwoman answered 2019-10-06T02:13:09Z
0 votes

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

我有一个父应用程序组件,其中有一个子Spinner组件。

应用程式会显示应用程式是否正在载入的状态。 加载应用程序时,将正常显示Spinner。 当应用未加载时(onAnimationEnd为假),使用道具setTimeout渲染Spinner。

App.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会显示其是否处于隐藏状态。 一开始,使用默认的道具和状态,微调器将正常渲染。 onAnimationEnd类将其动画设置为淡入淡出。Spinner接收到道具255668504951731010977时,将使用isHidden === true类进行渲染,以使其淡入淡出。

但是,我也希望该组件在褪色后卸下。

在这一点上,我尝试使用onAnimationEnd React合成事件,类似于上面的@ pranesh-ravi的解决方案,但是没有用。 取而代之的是,我使用setTimeout将状态设置为隐藏,且延迟时间与动画的长度相同。 延迟后,微调框将更新为isHidden === true,并且不会呈现任何内容。

这里的关键是,父级不卸载子级,它告诉子级何时卸载,而子级在完成其卸载业务后自行卸载。

Spinner.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;

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);
    }
}
mjw answered 2019-10-06T02:14:41Z
translate from https://stackoverflow.com:/questions/40064249/react-animate-mount-and-unmount-of-a-single-component