学習用にReact Transition Groupの内容を簡単にまとめなおしたものです。
全ての項目を網羅してはいません。
ソースはこちらです。
react-transition-group-sample
デモサイト
React Transition Group の概要
ReactでCSSアニメーションを扱う為のライブラリです。
具体的にフェードインやスライドインなどのアニメーションそのものは提供していません。
アニメーションの為のスタイルは自分で用意します。
その用意したスタイルを適切なタイミングでDOMに適用する為の、管理手法を提供しています。
インストール
# npm
npm install react-transition-group
#yarn
yarn add react-transition-group
Transition コンポーネント
基本のコンポーネントです。
props
の変化や時間の経過に応じて、子コンポーネントを再レンダリングします。
完成図
ボタンまたはカード本体のクリックで反転アニメーションを開始します。
アニメーション終了時に文字を変更します。
実装方法
Transition
コンポーネントを用います。
import { Transition } from 'react-transition-group';
// カードコンポーネントに適用するStyle
const FLIP_STYLE = {
// 前面⇒背面
entering: {
transition: 'all .5s ease',
transform: 'perspective(25rem) rotateY(-180deg)'
},
// 背面
entered: {
transition: '',
transform: 'perspective(25rem) rotateY(-180deg)'
},
// 背面⇒前面
exiting: {
transition: 'all .5s ease',
transform: 'perspective(25rem) rotateY(-360deg)'
},
// 前面
exited: {
transition: '',
transform: 'perspective(25rem) rotateY(0)'
}
};
// 反転するカードコンポーネント
const FlipCard = ({
flip,
flipToFront,
flipToBack
}) => {
const [text1, setText1] = useState('Moi!');
const [text2, setText2] = useState('Hei!');
const callBacks = {
onEnter: () => setText2('Hei!'),
onEntered: () => setText2('HeiHei!'),
onExit: () => setText1('Moi!'),
onExited: () => setText1('MoiMoi!')
};
// 「in」がアニメーションのトリガーとなる
return (
<Transition
in={flip}
timeout={550}
{...callBacks}
>
{state => (
<div className="flip-card" style={FLIP_STYLE[state]}>
<div
className="flip-card__front"
onClick={flipToBack}
>
{text1}
</div>
<div
className="flip-card__back"
onClick={flipToFront}
>
{text2}
</div>
</div>
)}
</Transition>
);
};
const TransitionPage = () => {
const [flip, setFlip] = useState(false);
return (
<div>
<FlipCard
flip={flip}
flipToBack={() => setFlip(true)}
flipToFront={() => setFlip(false)}
/>
<button
className="btn"
onClick={() => setFlip(!flip)}
>
Flip to {flip ? 'front' : 'back'}
</button>
</div>
);
};
Transition
コンポーネントにはchildren
として、Styleを適用したいコンポーネントを返すコールバック関数を渡します。
この関数は、props
のin (Boolean)
propsの値が変更された時と、それからtimeout (ms)
propsで指定した時間分経過後に呼ばれます。
その際に、各ステータスを示す文字列を引数で受け取ります。
|in|変更時|timeout経過後|
|:--|:--|:--|:--|
|true|'entering'|'entered'|
|false|'exiting'|'exited'|
上記例では、各ステータスをkeyとして適用したいstyleを定義した連想配列を予め用意し、それぞれのタイミングで適用しています。
また、各タイミングごとに実行されるコールバック関数を指定することも可能です。
引数には子コンポーネントのHTMLElementを受け取ります。
- onEnter: 'entering'適用前
- onEntering: 'entering'適用後
- onEntered: 'entered'適用後
- onExit: 'exiting'適用前
- onExiting: 'exiting'適用後
- onExited: 'exited'適用後
Transition | React Transition Group
CSSTransition コンポーネント
上記Transition
コンポーネントの機能を内包している拡張版です。
より細かくて複雑な指定が可能になります。
完成図
コールバック関数を利用して、3つのコンポーネントが連鎖してアニメーションしています。
実装方法
in
とtimeout
の値を基に、子コンポーネントを再レンダリングするところはTransition
と同様です。
異なるのは、children
にコールバック関数ではなく、コンポーネントを直接取ります。
そして、各タイミングで子コンポーネントのclass(className)
を書き換えます。
import { CSSTransition } from 'react-transition-group';
// 昇降する丸
const Slide = ({ show, ...callBack }) => (
<CSSTransition
in={show}
timeout={400}
unmountOnExit
classNames="pop__slide-"
{...callBack}
>
<div className="pop__slide"></div>
</CSSTransition>
);
// 回転する四角
const Rotate = ({ show, children, ...callBack }) => (
<CSSTransition
in={show}
timeout={300}
unmountOnExit
classNames="pop__rotate-"
{...callBack}
>
<div className="pop__rotate">
{children}
</div>
</CSSTransition>
);
// フェードインする枠
const Frame = ({ show, children, ...callBack }) => (
<CSSTransition
in={show}
timeout={200}
unmountOnExit
classNames="pop__frame-"
{...callBack}
>
<div className="pop__frame">
{children}
</div>
</CSSTransition>
);
const CSSTransitionPage = () => {
const [flame, setFlame] = useState(false);
const [rotate, setRotate] = useState(false);
const [slide, setSlide] = useState(false);
const [lock, setLock] = useState(false);
return (
<div className="pop">
<button
className="btn"
disabled={lock}
onClick={() => {
if (flame) {
setSlide(false);
setLock(true);
} else {
setFlame(true);
setLock(true);
}
}}
>
Click !
</button>
<Frame
show={flame}
onEntered={() => setRotate(true)}
onExited={() => setLock(false)}
>
<Rotate
show={rotate}
onEntered={() => setSlide(true)}
onExited={() => setFlame(false)}
>
<Slide
show={slide}
onEntered={() => setLock(false)}
onExited={() => setRotate(false)}
/>
</Rotate>
</Frame>
</div>
);
};
CSSTransition
のprops
には、in
, timeout
に加えてclassNames
も渡します。(s
がアルヨ)
この値は、子コンポーネントに適用するCSSクラス名のベースとなります。
各タイミングに応じて、classNames
の値に接尾辞をつけた名前を、子コンポーネントに適用します。
例)classNames="fade"
を指定した場合
|in|変更直後|'-enter'or'-exit'適用後|timeout経過後|
|:--|:--|:--|:--|:--|
|true|fade-enter|fade-enter-active|fade-enter-done|
|false|fade-exit|fade-exit-active|fade-exit-done|
その他、マウント時に適用される-appear-*もあります。
フェードアニメーションを適用させたい場合は、下記のようなCSSを準備します。
.fade-enter {
opacity: 0;
}
.fade-enter-active {
transition: all .2s ease-in-out;
opacity: 1;
}
.fade-exit {
opacity: 1; /* opacityの規定値は1だが、省略不可 */
}
.fade-exit-active {
transition: all .2s ease-in-out;
opacity: 0;
}
基本的にマウント直後はクラスは適用されていません。アニメーション終了後は*-done
が適用された状態です。
上記例では、マウント直後とfade-exit-done
適用後はコンポーネントが見えてます。
in
propsがfalse
の時にコンポーネントをアンマウントにしたい場合は、unmountOnExit
にtrue
を指定します。
CSSTransition | React Transition Group
TransitionGroup コンポーネント
Transition
またはCSSTransition
のリストを管理する為のコンポーネントです。
完成図
実装方法
子コンポーネントとなるCSSTransition
の実装方法はほとんど変わりませんが、トリガーであるin
propsは指定しません。この管理をTransitionGroup
に任せます。
import { TransitionGroup, CSSTransition } from 'react-transition-group';
// リスト項目
const Card = ({ onDelete, children }) => (
<li className="fade-card">
<div className="fade-card__body">
{children}
<button
type="button"
className="fade-card__del"
onClick={onDelete}
>
delete
</button>
</div>
</li>
);
// 項目追加用
const AddList = ({ addItem }) => {
const [value, setValue] = useState('');
const itemId = useRef(0);
return (
<form
className="fade-add"
onSubmit={e => {
e.preventDefault();
itemId.current = itemId.current + 1;
setValue('');
addItem({
id: itemId.current,
value
});
}}
>
<input
type="text"
className="fade-add__text"
value={value}
onChange={e => setValue(e.target.value)}
/>
<button
type="submit"
className="btn"
>
Add
</button>
</form>
);
};
// リスト管理
const TransitionGroupPage = () => {
const [list, setList] = useState([]);
return (
<div>
<AddList addItem={item => setList([item, ...list])} />
<TransitionGroup component="ul">
{list.map(({ id, value }) => (
// 「in」は指定しない
<CSSTransition
key={id}
timeout={200}
classNames="fade-card-"
>
<Card onDelete={() => setList(list.filter(item => item.id !== id))}>
{value}
</Card>
</CSSTransition>
))}
</TransitionGroup>
</div>
);
};
リストの追加削除は、通常のReactコンポーネントでリスト項目を扱う時と同様に、該当のstate
の配列に対して追加or削除するだけです。
TransitionGroup
によって、項目追加時(マウント時)にenter
の、削除時(アンマウント時)にexit
のスタイルが適用されます。
TransitionGroup
コンポーネントは、実際にHTMLタグがレンダリングされます。規定では<div>
です。
他のタグに変更したい場合は、component
の値に任意のタグ名を指定します。
タグの挿入を避けたい場合は、component={null}
を指定します。
TransitionGroup | React Transition Group
ReactRouter との併用
ReactRouterでルーティングする際に、React Transition Groupのアニメーションを適用する方法です。
ReactRouterの解説は割愛いたします。下記の記事がとても分かりやすいです。
react-router@v4を使ってみよう:シンプルなtutorial | Qiita
完成図
実装方法
ReactRouter
のRoute
コンポーネントのchildren
に、CSSTransition
コンポーネントを返す関数を渡します。
この関数はルーティングの度に各コンポーネント毎に呼ばれ、ルーティングに関連する情報を含むオブジェクトを引数で受け取ります。
このオブジェクトのmatch
プロパティは、ルートがマッチしているコンポーネントの時のみ値が存在し、その他の場合はnull
となります。
このmatch
の値をin
propsに適用することで、ルーティングに応じて各CSSTransition
コンポーネントのアニメーションがトリガーされるようになります。
同時にunmountOnExit
も指定しましょう。
import { BrowserRouter, Route, NavLink } from 'react-router-dom';
import { CSSTransition } from 'react-transition-group';
import TransitionPage from './transition';
import CSSTransitionPage from './cssTransition';
import TransitionGroupPage from './transitionGroup';
const ROUTES = [
{ name: 'Transition', path: '/', Component: TransitionPage },
{ name: 'CSSTransition', path: '/css', Component: CSSTransitionPage },
{ name: 'TransitionGroup', path: '/group', Component: TransitionGroupPage }
];
const App = () => {
return (
<div className="page">
<BrowserRouter>
<ul className="menu">
{ROUTES.map(({ name, path }) => (
<li key={path}>
<NavLink
to={path}
className="menu__item"
activeClassName="menu__item--active"
exact
>
{name}
</NavLink>
</li>
))}
</ul>
<div className="page__container">
{ROUTES.map(({ path, Component }) => (
<Route key={path} path={path} exact>
{({ match }) => (
<CSSTransition
in={match != null}
timeout={300}
classNames="page__item-"
unmountOnExit
>
<div className="page__item">
<Component />
</div>
</CSSTransition>
)}
</Route>
))}
</div>
</BrowserRouter>
</div>
);
};
ルーティング切替時、前後のコンポーネントが同時に存在することになります。
レイアウトによっては、position:absolute
などを用いて通常フローから切り離すなどの対応を取る必要があります。