web-dev-qa-db-ja.com

Reactコンポーネント間の遷移をアニメーション化する

最初のコンポーネントがフェードアウトし、次のコンポーネントがDOMに追加されてフェードインする前にDOMから削除される2つのコンポーネント間でアニメーションを作成します。それ以外の場合、新しいコンポーネントはDOMに追加され、古いコンポーネントは削除されます。このフィドルで問題を確認できます。

http://jsfiddle.net/phepyezx/4

// css snippet
.switch-enter {
    opacity: 0.01;
}
.switch-enter.switch-enter-active {
    opacity: 1.0;
}
.switch-leave {
    opacity: 1.0;
}
.switch-leave.switch-leave-active {
    opacity: 0;
}

// React snippet 
<ReactCSSTransitionGroup transitionName="switch">
    <div key={key} className={className}>{this.text()}</div>
</ReactCSSTransitionGroup>

受け入れられない解決策(私にとって)は、次のように新しいコンポーネントに移行する前に元のコンポーネントをcssで非表示にすることです。

http://jsfiddle.net/phepyezx/5

// Change to css
.switch-leave {
    visibility: hidden;
    height: 0px;
    width: 0px;
    opacity: 1.0;
}

元のコンポーネントが削除される前に、新しいコンポーネントをマウントしてから「遅延」反応する方法はありますか?これを実現するために、Velocityやその他のライブラリを公開しています。

ありがとう

15
Rick Jolly

componentWillUnmount()ライフサイクルメソッドを使用して解決しました。

http://jsfiddle.net/phepyezx/9/

コードは次のとおりです。

var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;

const Off = React.createClass({
    componentWillUnmount () {
        this.props.handleTransitionEnd();
    },
    render()  {
        return (
            <div className="off button">OFF</div>
        )
    }
});

const On = React.createClass({
    componentWillUnmount () {
        this.props.handleTransitionEnd();
    },
    render()  {
        return (
            <div className="on button">ON</div>
        )
    }
});

var Switch = React.createClass({
    getInitialState: function() {
        return {
            on: false,
            transitionEnd: true
        };
    },

    toggle: function(e) {
        this.setState({
            on: !this.state.on,
            transitionEnd: false
        });
    },

    handleTransitionEnd() {
        this.setState({transitionEnd: true});
    },

    renderOff() {
        if (! this.state.on && this.state.transitionEnd) {
            return (
                <Off key="off" handleTransitionEnd={this.handleTransitionEnd} />
            )
        }
    },

    renderOn() {
        if (this.state.on && this.state.transitionEnd) {
            return (
                <On key="on" handleTransitionEnd={this.handleTransitionEnd} />
            )
        }
    },

    render: function() {
        return (
            <div>
              <button onClick={this.toggle}>Toggle</button>
              <ReactCSSTransitionGroup transitionName="switch">
                {this.renderOff()}
                {this.renderOn()}
              </ReactCSSTransitionGroup>
            </div>
        );         
    }
});

React.render(<Switch/>, document.getElementById("switch"));

そして、関連するCSS:

.switch-enter {
    opacity: 0.01;
}
.switch-enter.switch-enter-active {
    opacity: 1.0;
    transition: opacity 500ms ease-in;
}
.switch-leave {
    opacity: 1.0;
}
.switch-leave.switch-leave-active {
    opacity: 0;
    transition: opacity 500ms ease-out;
}

Jonny Buchanan's answer で同じ効果的な結果を得ることができます。これは、componentWillUnmount()の代わりに絶対位置決めと遅延を使用します

11
Rick Jolly

別の解決策は、着信要素と発信要素が同じスペースを占めるようにすることです。たとえば、両方を絶対位置に配置することにより、

<ReactCSSTransitionGroup
    className="container"
    component="div"
    transitionName="switch">
...

.container {
    position: relative;
}
.container > div {
    position: absolute;
}

http://jsfiddle.net/phepyezx/7/


transition-delayを使用すると、入力コンポーネントを表示する前に、出力コンポーネントが消えるまで待機できます。例:

.fade-enter {
  opacity: 0.01;
}
.fade-enter.fade-enter-active {
  opacity: 1;
  transition: opacity 1s;
  transition-delay: 1s;
}

.fade-leave {
  opacity: 1;
}
.fade-leave.fade-leave-active {
  opacity: 0.01;
  transition: opacity 1s;
}
17
Jonny Buchanan

次のコンポーネントのレンダリングを遅らせたい場合は、次のようなものを使用できます。

import React, { Component } from 'react';

export default class DelayedRender extends Component {

    static propTypes = {
        delay: React.PropTypes.number.isRequired,
        children: React.PropTypes.element,
        className: React.PropTypes.string
    };

    constructor(props) {
        super(props);

        this.state = {
            render: false
        };
    }

    componentDidMount() {
        setTimeout(() => {
            const delayedClassNames = this.refs.noDelayed.className;
            this.setState({
                render: true,
                classNames: delayedClassNames
            });
        }, this.props.delay);
    }

    render() {
        const { children, className } = this.props;
        return this.state.render ?
            <div className={this.state.classNames}>{children}</div> :
            <div className={className} ref="noDelayed" ></div>;
    }
}

そして、あなたのレンダリングメソッドで:

const ROUTE_TRANSITION_TIME = 500;
const views = [];

if (shouldRenderDelayedRoute) {
    views.Push(
        <DelayedRender delay={ROUTE_TRANSITION_TIME} key="book">
            <A ref="book"/>
        </DelayedRender>
    );
} else {
    views.Push(<B key="library"/>);
} 

<ReactCSSTransitionGroup
   transitionEnterTimeout={ROUTE_TRANSITION_TIME}
   transitionLeaveTimeout={ROUTE_TRANSITION_TIME}
   transitionName="fade-transition"
                        >
    {views}
</ReactCSSTransitionGroup>
2
David Heidrich