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

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

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

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

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

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

斯丁JimDavaid2020/03/12 20:18:59

我也非常需要单一组件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>
    );
  }
}

西里前端Tom2020/03/12 20:18:59

使用react-move可以使进入和退出过渡的动画变得容易得多

codeandbox上的示例

神奇Davaid2020/03/12 20:18:59

这是我的2cents:感谢@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);
泡芙小卤蛋2020/03/12 20:18:59

我认为使用Transitionfrom react-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);
Harry小宇宙2020/03/12 20:18:59

成帧器运动

从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/03/12 20:18:59

这是基于此帖子的使用新的hooks 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>
        </>
    );
}

CodeSandbox链接。

神无Sam乐2020/03/12 20:18:59

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

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

this.state.show改变孩子们安装/卸载的时候了。

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

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

现在,作为this.state.show更改,我们可以感知道具更改getDerivedStateFromProps(componentWillReceiveProps)并创建中间渲染阶段来执行动画。

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

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

一旦我们检测show标志变化,我们进入准备阶段,我们计算出像必要的属性heightwidthReactDOM.findDOMNode.getBoundingClientRect()

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

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

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

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