133

この単純なことは簡単に達成できるはずですが、私はそれがどれほど複雑であるかについて頭を悩ませています。

私がやりたいのは、React コンポーネントのマウントとアンマウントをアニメーション化することだけです。これまでに試したことと、各ソリューションが機能しない理由は次のとおりです。

  1. ReactCSSTransitionGroup- CSS クラスはまったく使用していません。すべて JS スタイルなので、これは機能しません。
  2. ReactTransitionGroup- この下位レベルの API は優れていますが、アニメーションの完了時にコールバックを使用する必要があるため、CSS トランジションを使用するだけでは機能しません。常にアニメーション ライブラリがあり、次のポイントにつながります。
  3. GreenSock - ライセンスは、IMO のビジネス利用には制限が強すぎます。
  4. React Motion - これは素晴らしいようですが、TransitionMotion非常に紛らわしく、必要なものに対して非常に複雑です。
  5. もちろん、マテリアル UI のように、要素がレンダリングされても非表示のまま ( left: -10000px) のトリックを実行することはできますが、そのルートには行きたくありません。私はそれをハックだと考えており、コンポーネントをアンマウントしてクリーンアップし、DOM を乱雑にしないようにしたいと考えています。

簡単に実装できるものが欲しい。マウント時に、一連のスタイルをアニメーション化します。アンマウント時に、同じ (または別の) スタイルのセットをアニメーション化します。終わり。また、複数のプラットフォームで高性能でなければなりません。

ここで壁にぶち当たりました。何か不足していて、これを行う簡単な方法がある場合は、お知らせください。

4

19 に答える 19

122

これは少し長くなりますが、すべてのネイティブ イベントとメソッドを使用してこのアニメーションを実現しました。いいえなどReactCSSTransitionGroup_ReactTransitionGroup

私が使用したもの

  • React ライフサイクル メソッド
  • onTransitionEndイベント

これがどのように機能するか

  • 渡された mount prop( ) に基づいて要素をマウントし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>

于 2016-10-21T08:49:29.940 に答える
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 に答える
0

これは、あなたが言及したライブラリと同じように、のCSSTransitionコンポーネントを使用して簡単に実行できます。秘訣は、通常のように表示/非表示メカニズムなしでCSSTransitionreact-transition-groupコンポーネントをラップする必要があることです。そうしないと、アニメーションが非表示になり、機能しません。例:{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

シンプルなフックの例を探している場合:

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 に答える