【React】ReactでCSSアニメーション(react-transition-group の利用)
最近は、UIフレームワークばかりを使っていて、独自でアニメーションを実装する機会がなかったが、React で簡単なタブ切り替えのスライドイン・アウトを実装する機会があったので、React での CSS アニメーション実装に関して備忘録を残しておく。
react-transition-group
本ライブラリは、React の公式でも紹介されている、React で CSSアニメーションを実装するためのライブラリである。ただし、スライドイン・アウトなどのアニメーション、そのものは提供されておらず、CSSアニメーションは自分たちで実装する必要がある。CSSアニメーションを適切なタイミングでDOMに適応させる管理手法を本ライブラリでは提供してくれている。
Vue の場合、transition
ラッパーコンポーネントがデフォルトで提供されているが、まさに同じようなライブラリである。
本ライブラリには、4つのラッパーコンポーネントが存在する。
- Transition
- CSSTransition
- TransitionGroup(Transition or CSSTransition のラッパーコンポーネント)
- SwitchTransition(Transition or CSSTransition のラッパーコンポーネント)
インストール方法
# npm npm install react-transition-group --save # yarn yarn add react-transition-group
1. Transition
Transitionは、<Transition>
の Props in
の変化と時間 timeout
の経過に応じて、stateが以下の4つのstateに変化し、子コンポーネントを再レンダリングする。子コンポーネントでは、4つのstateを受け取り、それぞれの css スタイルを用意しておくことで、それぞれのタイミングで適応させることができる。
state | inの変化によるCSS適応タイミング |
---|---|
entering | false→trueの変更時 |
entered | false→trueになり、timeout経過後 |
exiting | true→falseの変更時 |
exited | true→falseになり、timeout経過後 |
また、各タイミングごとに実行されるコールバック関数も設定することができる。(以下では、enterのみ記載しているが、exitも同様に存在する。)
- onEnter:entering適用前
- onEntering:entering適用後
- onEntered:entered適用後
以下の例では、inPropの変化に応じて、フェードイン/アウトを行っている。cssのtransitionとtimeoutの設定で様々な表現が実現可能となる。
import React, { useState } from 'react' import { Transition } from 'react-transition-group' const page = () => { const [inProp, setInProp] = useState(false) const transitionStyles = { entering: { opacity: 1, color: 'red', transition: 'all 1s ease' }, entered: { opacity: 1, color: 'blue' }, exiting: { opacity: 0, transition: 'all 1s ease' }, exited: { opacity: 0 }, } return ( <> <Transition in={inProp} timeout={1500}> {(state) => ( <div style={transitionStyles[state]}> <p>React CSS Animation</p> </div> )} </Transition> <button style={{ marginTop: '10px' }} onClick={() => setInProp(!inProp)}> Click </button> </> ) } export default page
2. CSSTransition
CSSTransition は、Transition
の拡張版のコンポーネントである。公式には、Angular.js の ngAnimate ライブラリにインスパイヤされたコンポーネントと書いてある。CSSトランジションやアニメーションを使う場合は、このコンポーネントを使用する方が簡単に様々な実装が可能となる。CSSTransitionでは、状態遷移中、開始および終了の状態中にクラス名を適用する。
-appear
も存在して、inの変化ではなく、マウント時に適用される。注意点として、in={false} でマウントした場合は、何もクラスが適応されない。(-exit-done
が適応される気がしたが違うので、注意とのこと。アニメーション終了後に、-done
が適用される。)
また、in={false} の時にコンポーネントをアンマウントにしたい場合は、unmountOnExit={true}を指定する。以下の例では、Transitionと同じようにフェードイン/アウトを行っているが、 unmountOnExit
を指定しないと、マウント時にはクラスが適応されないので、クラスが何も適応されていないpタグがマウントされ、文字が普通に表示されるようになってしまう。
import React, { useState } from 'react' import { CSSTransition } from 'react-transition-group' import styled from 'styled-components' const page = () => { const [inProp, setInProp] = useState(false) return ( <Root> <CSSTransition in={inProp} timeout={1500} classNames="fade" unmountOnExit> <p>React CSS Animation</p> </CSSTransition> <button style={{ marginTop: '10px' }} onClick={() => setInProp(!inProp)}> Click </button> </Root> ) } const Root = styled.div` .fade-enter { opacity: 0; } .fade-enter-active { transition: all 1s ease; color: red; opacity: 1; } .fade-enter-done { color: blue; opacity: 1; } .fade-exit { opacity: 1; } .fade-exit-active { transition: all 1s ease; opacity: 0; } ` export default page
3. TransitionGroup
TransitionGroup は、Transition または CSSTransition のリストを管理する為のコンポーネントである。これを使うことで、タブ切り替えなどを実装することができる。
import React, { useState } from 'react' import { TransitionGroup, CSSTransition } from 'react-transition-group' import styled from 'styled-components' const page = () => { const [tabIndex, setTabIndex] = useState(0) const tabs = [ { index: 0, content: 'content 0' }, { index: 1, content: 'content 1' }, { index: 2, content: 'content 2' }, { index: 3, content: 'content 3' }, ] return ( <Root> <div className="tabs"> {tabs.map((tab) => ( <button key={tab.index} onClick={() => setTabIndex(tab.index)}> {tab.index} </button> ))} </div> <TransitionGroup className="wrapper"> <CSSTransition key={tabIndex} classNames="slide" timeout={1500}> <div className="main">{tabs[tabIndex].content}</div> </CSSTransition> </TransitionGroup> </Root> ) } const Root = styled.div` .slide-enter { transform: translateX(100%); } .slide-enter-active { transform: translateX(0%); transition: transform 1500ms ease-in-out; } .slide-exit { transform: translateX(0%); } .slide-exit-active { transform: translateX(-100%); transition: transform 1500ms ease-in-out; } padding: 5px; height: 100vh; display: flex; flex-direction: column; .tabs { display: grid; grid-template-columns: repeat(4, 1fr); } .wrapper { position: relative; border: slid 1px #444; flex: 1; } .main { position: absolute; top: 0; left: 0; right: 0; bottom: 0; padding: 10px; } ` export default page
4. SwitchTransition
SwitchTransition は、TransitionGroup のコンポーネントである。公式には、Vue.js の transition modes にインスパイヤされたコンポーネントと書いてある。SwitchTransition には、以下の2つのモードがある。
mode | 説明 |
---|---|
out-in | 古いコンポーネントが削除されるまで待機してから、新しいコンポーネントを挿入する。 |
in-out | 最初に新しいコンポーネントを挿入し、挿入後に、古いコンポーネントを削除する。 |
「Switch」 と書いてあるように、用途は限定的である。新しいコンポーネントと古いコンポーネントに対して、同時にアニメーションを実行できないので、同時に実行したい場合は、TransitionGroup を使うべきである。
上記のタブ切り替えを SwitchTransition に変えた場合、滑らかというよりは、ワンテンポ遅れている感じがして少し違和感がある。用途に応じて使うべきであると思う。
まとめ
今回は、ReactでCSSアニメーションをどのように実装すべきかを整理した。react-transition-group は非常に使いやすいと思えた。
それでは、ステキな開発ライフを。