全ての記事

react-routerに簡単なtransitionをつける

2017-03-09 @sunderls

react

最近Reactでweloggerを開発していて、ページ切り替えるときトランジションをつけたいなと思って、いかはちょっとしたメモです。

1. 先ずはトランジションがないアプリを立ち上げる

ここはcreate-react-appを使う

> npm install -g create-react-app
> create-react-app transition
> cd transition
> npm start

で、できた。

2. 二つのページAとBを用意する

加入router

npm install --save-dev react-router

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, IndexRoute, browserHistory } from 'react-router'
import App from './App';
import A from './A';
import B from './B';

ReactDOM.render(
    <Router history={browserHistory}>
        <Route path="/" component={App}>
            <IndexRoute component={A} />
            <Route path="A" component={A} />
            <Route path="B" component={B} />
        </Route>
    </Router>,
    document.getElementById('root')
);

app.js

import React from 'react';
import { Link } from 'react-router'

export default (props) => (
  <div>
    <Link to="/A">to A</Link><Link to="/B" style={{paddingLeft:'100px'}}>to B</Link>
    {props.children}
  </div>
  );

A/index.js

import React from 'react';
export default (props) => (
    <div>page A</div>
)

B/index.js

import React from 'react';
export default (props) => (
    <div>page B</div>
)

各自のリンクを踏むと、それぞれのページコンテンツが表示される。でもパッと終わって、何もトランジションがないです。

3. 右からすーっと入ってくるように

うん、react自身のtransitionが使えそう。ホームページのサンプルを作ってみよう: App.js

import './App.css';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
export default (props) => {
    return <div>
        <Link to="/A">to A</Link><Link to="/B" style={{paddingLeft:'100px'}}>to B</Link>
        <ReactCSSTransitionGroup
            transitionName="slide"
            transitionEnterTimeout={500}
            transitionLeaveTimeout={300}>
            {props.children}
        </ReactCSSTransitionGroup>
    </div>
}

App.css

.slide-enter {
    transform: translateX(100%);
}

.slide-enter.slide-enter-active {
    transform: translateX(0%);
    transition: transform 500ms ease-in;
}

.slide-leave {
    transform: translateX(0%);
}

.slide-leave.slide-leave-active {
    transform: translateX(-100%);
    transition: transform 300ms ease-in;
}

で、ダメでした。調べたらkeyが原因でした、一つだけのchildをトランジションにしてもkeyが必要で、ランダムでつけよう

结果,没反应。原来单独transit一个child的话,需要给加上key,先随机加一个key

<ReactCSSTransitionGroup
    transitionName="slide"
    transitionEnterTimeout={500}
    transitionLeaveTimeout={300}>
    {React.cloneElement(props.children, {
        key: Math.random()
    })
</ReactCSSTransitionGroup>

動くけど、ページBでBのリンクを踏んでもトランジションが発生する。これはおそらくkeyが変わるのが原因、pathnameにしよう

 <ReactCSSTransitionGroup
    transitionName="slide"
    transitionEnterTimeout={500}
    transitionLeaveTimeout={300}>
    {React.cloneElement(props.children, {
        key: props.location.pathname
    })}
</ReactCSSTransitionGroup>

順調!ちょっと改善します。まず、親ノードを<div>にして、もう一つのposition:relativeの親ノードを追加。

<div className="container">
    <ReactCSSTransitionGroup
        component="div"
        transitionName="slide"
        transitionEnterTimeout={500}
        transitionLeaveTimeout={300}>
        {React.cloneElement(props.children, {
            key: props.location.pathname
        })}
    </ReactCSSTransitionGroup>
</div>

トランジションは同じ垂直位置にするので、animationの中でpositionの宣言をたす。

.container {
  position: relative;
}

.slide-enter {
    position: absolute;
    width: 100%;
    transform: translateX(100%);
}

.slide-leave {
    position: absolute;
    width: 100%;
    transform: translateX(0%);
}

完璧。

4. Aへ戻る時逆方向にしたい?

まず、トランジションはページ自身の属性にするか?いや、もし場合によって変わる可能性が?

なので、transitionの指定はページにするのではなく、リンクを踏む時に指定するのが適切だと思う。そのために<a>の代わりに、<transLink>を作る、踏むと次のトランジションを変えるって感じ。

transLink.js

import React from 'react';
import config from './config';

export default class TransLink extends React.PureComponent {
    onClick() {
        config.transition = this.props.transition || 'right';
    }

    render() {
        return <span onClick={this.onClick.bind(this)}>{this.props.children}</span>;
    }
}

config.js

export default {
    transition: 'right'
}

configがグローバル変数で、次はアプリを修正する。 app.js

import React from 'react';
import './App.css';
import { Link } from 'react-router'
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import config from './config';
import TransLink from './transLink'

export default (props) => {
    return <div>
        <TransLink transition="left">
            <Link to="/A">to A</Link>
        </TransLink>
        <TransLink transition="right">
            <Link to="/B" style={{paddingLeft:'100px'}}>to B</Link>
        </TransLink>
        <div className="container">
            <ReactCSSTransitionGroup
                component="div"
                transitionName={config.transition}
                transitionEnterTimeout={500}
                transitionLeaveTimeout={300}>
                {React.cloneElement(props.children, {
                    key: props.location.pathname
                })}
            </ReactCSSTransitionGroup>
        </div>
    </div>
}

cssに二種類のslide animationを用意する。

以及css中,增加两种方向的slide

.right-enter {
    position: absolute;
    width: 100%;
    transform: translateX(100%);
}

.right-enter.right-enter-active ,
.left-enter.left-enter-active {
    transform: translateX(0%);
    transition: transform 500ms ease-in;
}

.right-leave, .left-leave {
    position: absolute;
    width: 100%;
    transform: translateX(0%);
}

.right-leave.right-leave-active {
    transform: translateX(-100%);
    transition: transform 300ms ease-in;
}

.left-enter {
    position: absolute;
    width: 100%;
    transform: translateX(-100%);
}

.left-leave.left-leave-active {
    transform: translateX(100%);
    transition: transform 300ms ease-in;
}

オーケー、無事で終わった!

5 まとめ

まずは、どの部分がトランジションの対象にするかを確かめましょう、その共通の親ノードにつけるのが適切です。pathnameをkeyにして、css transitionをつける。animationについて、とても人気なライブライーがあるーreact-motion、まだ触れてないけど、今後のいつかにご楽しみ。