[animation] React-단일 구성 요소의 마운트 및 마운트 해제 애니메이션

이 간단한 일은 쉽게 해낼 수 있어야하지만, 그것이 얼마나 복잡한 지에 대해 머리를 뽑아 내고 있습니다.

제가하고 싶은 것은 React 컴포넌트의 마운트와 언 마운트를 애니메이션화하는 것뿐입니다. 지금까지 시도한 내용과 각 솔루션이 작동하지 않는 이유는 다음과 같습니다.

  1. ReactCSSTransitionGroup -CSS 클래스를 전혀 사용하지 않고 모든 JS 스타일이므로 작동하지 않습니다.
  2. ReactTransitionGroup-이 하위 수준 API는 훌륭하지만 애니메이션이 완료되면 콜백을 사용해야하므로 CSS 전환 만 사용하면 여기서는 작동하지 않습니다. 다음 지점으로 이어지는 애니메이션 라이브러리는 항상 있습니다.
  3. GreenSock-라이선스가 업무용 IMO에 너무 제한적입니다.
  4. React Motion-이것은 훌륭해 보이지만 TransitionMotion내가 필요한 것에 대해 매우 혼란스럽고 지나치게 복잡합니다.
  5. 물론 머티리얼 UI처럼 속임수를 쓸 수 있습니다. 요소는 렌더링되지만 숨겨진 상태로 유지되는 ( left: -10000px)하지만 그 길을 가고 싶지 않습니다. 나는 그것이 해키라고 생각 하고 내 구성 요소가 마운트 해제되어 DOM을 정리하지 않고 정리 하고 싶습니다.

구현 하기 쉬운 것을 원합니다 . 마운트시 스타일 세트에 애니메이션을 적용합니다. 마운트 해제시 동일한 (또는 다른) 스타일 세트에 애니메이션을 적용합니다. 끝난. 또한 여러 플랫폼에서 고성능이어야합니다.

여기 벽돌 벽에 부딪 혔어요. 내가 뭔가를 놓치고 이것을 할 수있는 쉬운 방법이 있다면 알려주세요.



답변

이것은 약간 길지만이 애니메이션을 달성하기 위해 모든 네이티브 이벤트와 메서드를 사용했습니다. 아니요 ReactCSSTransitionGroup,ReactTransitionGroup

내가 사용한 것들

  • 반응 수명주기 방법
  • onTransitionEnd 행사

작동 원리

  • 전달 된 마운트 소품 ( mounted)과 기본 스타일 ()을 기반으로 요소를 마운트합니다.opacity: 0 )을
  • 마운트 또는 업데이트 후 componentDidMount( componentWillReceiveProps추가 업데이트 용)을 opacity: 1사용하여 시간 초과 (비 동기화)로 스타일 ( ) 을 변경합니다 .
  • 마운트 해제 중에 컴포넌트에 소품을 전달하여 마운트 해제를 식별하고 스타일을 다시 변경 ( opacity: 0),, onTransitionEndDOM에서 요소 마운트 해제를 제거합니다.

주기를 계속하십시오.

코드를 살펴보면 이해할 수있을 것입니다. 설명이 필요하면 의견을 남겨주세요.

도움이 되었기를 바랍니다.

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>


답변

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>


답변

다음은 구성 요소의 마운트 해제 단계를 지연시키기 위해이 게시물을 기반으로 하는 새로운 후크 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 링크.


답변

작업하는 동안이 문제에 대응했고, 단순 해 보이지만 실제로는 React에 없습니다. 다음과 같은 것을 렌더링하는 일반적인 시나리오에서 :

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

this.state.show변경 아이들은 멀리 / 언 마운트 권리가 장착되어있다.

내가 취한 한 가지 접근 방식은 래퍼 구성 요소를 만들고 Animate다음과 같이 사용하는 것입니다.

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

이제 this.state.show변경 사항으로 소품 변경을 감지 getDerivedStateFromProps(componentWillReceiveProps)하고 중간 렌더링 단계를 만들어 애니메이션을 수행 할 수 있습니다.

단계주기는 다음과 같습니다.

Static Stage 부터 시작합니다자식이 마운트되거나 마운트 해제 될 때 합니다.

우리가 감지되면 show플래그 변경, 우리는 입력 준비 단계 우리가 같은 필요한 특성 계산 heightwidth에서를 ReactDOM.findDOMNode.getBoundingClientRect().

그런 다음 Animate State에 들어가면 CSS 전환을 사용하여 높이, 너비 및 불투명도를 0에서 계산 된 값으로 (또는 마운트 해제시 0으로) 변경할 수 있습니다.

전환이 끝나면 onTransitionEndAPI를 사용 하여 다시 Static단계 로 변경
합니다.

단계가 원활하게 전송되는 방법에 대한 자세한 내용이 있지만 전체적인 아이디어 일 수 있습니다.

관심있는 사람이 있다면 React 라이브러리 https://github.com/MingruiZhang/react-animate-mount 를 만들어 솔루션을 공유했습니다. 모든 피드백 환영 🙂


답변

Transitionfrom을 사용 하는 react-transition-group것이 마운트 / 마운트 해제를 추적하는 가장 쉬운 방법 이라고 생각 합니다. 매우 유연합니다. 사용이 얼마나 쉬운 지 보여주기 위해 몇 가지 클래스를 사용하고 있지만, addEndListenerprop을 활용하여 자신 만의 JS 애니메이션을 확실히 연결할 수 있습니다 .

샌드 박스 : 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);


답변

프레이머 모션

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


답변

리 액트 모션을 고려하는 사람들에게 단일 구성 요소를 마운트 및 마운트 해제 할 때 애니메이션을 적용하는 것은 설정하는 데 부담이 될 수 있습니다.

이 프로세스를 훨씬 쉽게 시작할 수 있도록하는 react-motion-ui-pack 이라는 라이브러리 가 있습니다. 리 액트 모션을 둘러싼 래퍼입니다. 즉, 라이브러리에서 모든 이점을 얻을 수 있습니다 (즉, 애니메이션을 중단 할 수 있고 동시에 여러 언 마운트를 수행 할 수 있음).

용법:

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 라이브러리가 더 이상 어렵지 않을 수 있습니다.