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


103

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

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

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

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

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


여기서 우리는 어떤 종류의 애니메이션을 이야기하고 있습니까?
Pranesh Ravi

에서 CSS 불투명도 페이드와 같은 그냥 뭔가 단순transform: scale
ffxsam

요점 1과 2는 나를 혼란스럽게합니다. 어떤 종류의 애니메이션을 사용하고 있습니까? JS 전환 또는 CSS 전환?
Pranesh Ravi

1
하지 마십시오 혼동 CSS 스타일 / 클래스 (예 : .thing { color: #fff; }JS의 스타일) ( const styles = { thing: { color: '#fff' } }))
ffxsam

그러나 문제는 자바 스크립트를 사용하여 스타일을 변경하려고 할 때 실제로 전환을 제공하지 않는 요소의 스타일을 바꾸는 것입니다.
Pranesh Ravi

답변:


104

이것은 약간 길지만이 애니메이션을 달성하기 위해 모든 네이티브 이벤트와 메서드를 사용했습니다. 아니요 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>


감사합니다! 어디서 배웠 onTransitionEnd습니까? React 문서에서 볼 수 없습니다.
ffxsam

@ffxsam facebook.github.io/react/docs/events.html 전환 이벤트 아래에 있습니다.
Pranesh Ravi

1
그것이 무엇을했는지 어떻게 알았습니까? 문서는 아무것도 설명하지 않습니다. 또 다른 질문 : componentWillReceiveProps무언가를 반환 할 수 있다는 것을 어떻게 알았 습니까? 더 자세한 내용은 어디에서 읽을 수 있습니까?
ffxsam 2016-10-24

1
@ffxsam onTransitionEnd는 기본 JavaScript 이벤트입니다. 당신은 그것에 대해 구글 수 있습니다. facebook.github.io/react/docs/… 는 componentWillReceiveProps에 대한 아이디어를 제공합니다.
Pranesh Ravi 2016

7
BTW 나는 당신의 코드에 실수가 있다고 생각합니다. 당신의에서 Parent구성 요소, 당신은 참조this.transitionEnd
ffxsam

15

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>

1
@ffxsam을 어떻게 마운트 / 마운트 해제합니까?

어떻게 componentWillLeave()componentWillEnter()호출하기 AnimatedMount?
ROKIT

나를 위해 작동하지 않습니다, 여기 내 샌드 박스 : codesandbox.io/s/p9m5625v6m
Webwoman

15

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


2
우아한 솔루션, 댓글을 추가했다면 좋을 것입니다. :)
Webwoman

또한 javascript의 확장에서 잘 작동하기 때문에 typescrypt의 확장을 사용하는 이유는 무엇입니까?
Webwoman

또한 콘솔이 "네임 스페이스 NodeJS 시간 초과를 찾을 수 없음"을 반환합니다
Webwoman

1
@Webwoman 귀하의 의견에 감사드립니다. "NodeJS 시간 초과"로보고 된 문제를 재현 할 수 없습니다. 답변 아래의 내 CodeSandbox 링크를 참조하십시오. TypeScript와 관련하여 개인적으로 JavaScript보다 사용하는 것을 선호하지만 둘 다 물론 실행 가능합니다.
deckele

10

작업하는 동안이 문제에 대응했고, 단순 해 보이지만 실제로는 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 를 만들어 솔루션을 공유했습니다. 모든 피드백 환영 :)


귀하의 의견에 감사드립니다. 조잡한 답변에 대해 죄송합니다. 나는 내 대답에 더 많은 세부 사항과 다이어그램을 추가했으며 이것이 다른 사람들에게 더 도움이되기를 바랍니다.
Mingrui Zhang

2
@MingruiZhang 의견을 긍정적으로 받아들이고 답변을 개선 한 것을 보니 반갑습니다. 보는 것은 매우 상쾌합니다. 잘 했어.
버그

6

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

1
스타일이 지정된 구성 요소를 사용하는 경우 스타일이 지정된 구성 요소에 showprop을 전달 H1하고 모든 로직을 수행 할 수 있습니다 . 좋아요 ...animation: ${({ show }) => show ? entranceKeyframes : exitKeyframes} 300ms ease-out forwards;
Aleks

3

프레이머 모션

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

1

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

이 프로세스를 훨씬 쉽게 시작할 수 있도록하는 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 라이브러리가 더 이상 어렵지 않을 수 있습니다.


프로젝트가 더 이상 유지되지 않습니다 (2018)
Micros


1

이 작업 은 앞서 언급 한 라이브러리와 같은의 CSSTransition구성 요소를 사용하여 쉽게 수행 할 수 있습니다 react-transition-group. 트릭은 당신이 CSSTransition 구성 요소를 포장 할 필요가있다 당신이 일반적으로 것처럼 표시 / 숨기기 메커니즘없이 .IE {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;
}

0

여기 내 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);

0

2019 년에 로딩 스피너를 만들면서이 문제를 해결 한 방법은 다음과 같습니다. React 기능 구성 요소를 사용하고 있습니다.

하위 Spinner 구성 요소 가있는 상위 구성 요소가 있습니다 .

에는 앱이로드 중인지 여부에 대한 상태가 있습니다. 앱이로드되면 Spinner 가 정상적으로 렌더링됩니다. 앱이로드되지 않을 때 ( isLoadingfalse) Spinner 가 소품으로 렌더링됩니다.shouldUnmount .

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 가 정상적으로 렌더링됩니다. Spinner-fadeIn클래스 애니메이션 그것은 페이딩. 때 스피너 소품 수신 shouldUnmount가로 렌더링을Spinner-fadeOut 이 페이드 아웃 애니메이션, 대신 클래스입니다.

그러나 나는 또한 페이드 아웃 후 컴포넌트를 마운트 해제하기를 원했습니다.

이 시점 onAnimationEnd에서 위의 @ pranesh-ravi의 솔루션과 유사한 React 합성 이벤트를 사용해 보았지만 작동하지 않았습니다. 대신 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);
    }
}

0

또한 단일 구성 요소 Animation이 절실히 필요했습니다. React Motion을 사용하는 데 지 쳤지 만 사소한 문제로 머리카락을 잡아 당겼습니다 .. (i thing). 인터넷 검색을 한 후 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>
    );
  }
}


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클래스 이름 과을 위해 사용 <Container/>되지만 둘 다 사용자 정의 CSS 및 일반 오래된 <div>.


0

내가 사용하는 경우 Velocity또는 AnimeJS직접 (대신 애니메이션 노드에 라이브러리 csssetTimeout), 나는 내가 디자인 할 수 있습니다 발견 hook애니메이션 상태 제공 on과 기능을onToggle 애니메이션 (예. slidedown, 페이드) 킥오프를.

기본적으로 어떤 후크가하는 것은과 애니메이션 떨어져 전환,하는 것입니다 이후 업데이트 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()
  })
}


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.