react-transition-group
react-transition-groupはReactでCSSアニメーションを扱う為のライブラリです。
見栄えのするモーション自体を提供してくれるわけではなく、CSSを適用するタイミングを提供してくれるので、自分でアニメーションのCSSを書いてアニメーションさせます。
下記の4つのコンポーネントが提供されます。
- Transition
- CSSTransition
- SwitchTransition
- TransitionGroup
今回はcreate-react-appを使い、CSS(SCSS)はCSSModulesを使っていきます。
※レンダリング回数削減などパフォーマンス面については扱いません。
インストール
# create-react-appのインストール
npx create-react-app プロジェクト名
# プロジェクト直下に移動
cd プロジェクト名
# node-sassのインストール(scssではなくcssを使う場合は不要です。)
npm i -D node-sass
# react-transition-groupのインストール
npm i -S react-transition-group
# 起動
npm start
src/App.jsの不要な部分を削除します。
import React from "react";
import "./App.css";
function App() {
return (
<div className="App">
{/* ここにこれから作るコンポーネントを配置 */}
</div>
);
}
export default App;
Transition
シンプルなトランジション
どのタイミングでinと状態が変化しているか視覚化しています。
import React, { useState } from "react";
import { Transition } from "react-transition-group";
import Style from "./singleTransition.module.scss";
//トランジションのスタイル4種類を定義(使わないものは省略可能)
const transitionStyle = {
entering: {
transition: "all 1s ease",
transform: "translateY(220px) ",
backgroundColor:"red"
},
entered: {
transition: "all 1s ease",
transform: "translateY(220px) ",
backgroundColor:"green"
},
exiting: {
transition: "all 1s ease",
transform: "translateY(0)",
backgroundColor: "blue",
},
exited: {
transition: "all 1s ease",
transform: "translateY(0)",
backgroundColor: "gray",
},
};
//SingleTransitionコンポーネント
const SingleTransition = () => {
//マウントの状態を管理
const [mount, setMount] = useState(false);
//マウントのオンオフを切り替える
const changer = () => {
setMount(!mount);
};
return (
<div className={Style.wrapper}>
<button onClick={changer}>inの切り替え</button>
<div className={Style.circleGroup}>
<div className={Style.circleMember}>
<Transition in={mount} timeout={1000} >
{(state) =>
<div className={Style.circleShape} style={transitionStyle[state]} >
<div>
<p className={Style.circleText}> {mount ? "in=true" : "in=false"}</p>
<p className={Style.circleText}> {state}</p>
</div>
</div>}
</Transition>
</div>
</div>
</div>
);
}
export default SingleTransition;
.circleGroup {
display: flex;
justify-content: space-between;
margin: 100px auto;
width: 1000px;
}
.circleMember {
text-align: center;
width: 100px;
}
.circleShape {
align-items: center;
background-color: red;
border-radius: 50px;
display: flex;
height: 100px;
justify-content: center;
width: 100px;
}
.circleText {
color: white;
}
button {
margin: 0 auto;
display: block;
}
import React from "react";
import SingleTransition from "./components/singleTransition/SingleTransition";
function App() {
return (
<div className="App">
<ChainTransition />
</div>
);
}
export default App;
classNameでstateを含むクラス名を指定することで、transitionの状態によって独自のクラスを適用する事もできます
必須のProps
in
inの状態 | 結果 |
---|---|
inがtrueになる | マウント開始 |
inがfalseになる | アンマウント開始 |
timeout
entering、exitingのトランジションを使う場合で、addEndListenerを設定しない場合は必須です。
timeoutの指定による状態の変化
状態 | 初期 | timeoutで指定した時間経過後 |
---|---|---|
マウント時 | entering | entered |
アンマウント時 | exiting | exited |
各transitionに個別にタイムアウトを指定することもできます。
timeout={{
appear: 500,
enter: 300,
exit: 500,
}}
addEndListener
entering、exitingのトランジションを使う場合、timeoutを設定しない場合は必須です。
カスタムのtransition終了トリガーを追加して、動的にtimeout時間を設定したい場合に使用します。
<Transition in={mount} {...callBacks} timeout={1000} >
//↓
<Transition in={mount} {...callBacks} addEndListener={(node, done) => node.addEventListener("transitionend", done, false)}>
inがtrueになる度に追加されるので、毎回doneを呼ぶタイミングでイベントの解除が必要です。
timeoutとaddEndListener
timeoutoとaddEndListenerをどちらも指定しないとコンポーネントのトランジションはenteredとexitedだけを繰り返します。
両方指定した場合はaddEndListenerはフォールバックとして使用されます。
4つの状態
inとtimeoutの組み合わせで、コンポーネントに4つの状態が提供されます。
使用しないものについては、省略可能です。
- entering
- enterd
- exiting
- exited
enteringとexitingのtransitionの時間の長さについては、通常コンポーネントのtimeoutの値とそろえますが、あえて違う値にすることもできます。
inとtimeout以外のProps
import React, { useState } from "react";
import { Transition } from "react-transition-group";
import Style from "./otherProps.module.scss";
//アニメーションのスタイル4種類を定義(使わないものは省略可能)
const transitionStyle = {
entering: {
transition: "all 1s ease",
transform: "translateY(220px) ",
backgroundColor:"red"
},
entered: {
transition: "all 1s ease",
transform: "translateY(220px) ",
backgroundColor:"green"
},
exiting: {
transition: "all 1s ease",
transform: "translateY(0)",
backgroundColor: "blue",
},
exited: {
transition: "all 1s ease",
transform: "translateY(0)",
backgroundColor: "gray",
},
};
const OtherProps = () => {
//マウントの状態を管理
const [mount, setMount] = useState(false);
//マウントのオンオフを切り替える
const changer = () => {
setMount(!mount);
};
return (
<div className={Style.wrapper}>
<button onClick={changer}>inの切り替え</button>
<div className={Style.circleGroup}>
<div className={Style.circleMember}>
<p>Normal</p>
<Transition in={mount} timeout={1000} >
{(state) => <div className={Style.circleShape} style={transitionStyle[state]} >
<div>
<p className={Style.circleText}> {mount ? "in=true" : "in=false"}</p>
<p className={Style.circleText}> {state}</p>
</div>
</div>}
</Transition>
</div>
<div className={Style.circleMember}>
<p>mountOnEnter</p>
<Transition in={mount} timeout={1000} mountOnEnter >
{(state) => <div className={Style.circleShape} style={transitionStyle[state]} >
<div>
<p className={Style.circleText}> {mount ? "in=true" : "in=false"}</p>
<p className={Style.circleText}> {state}</p>
</div>
</div>}
</Transition>
</div>
<div className={Style.circleMember}>
<p>unmountOnExit</p>
<Transition in={mount} timeout={1000} unmountOnExit >
{(state) => <div className={Style.circleShape} style={transitionStyle[state]} >
<div>
<p className={Style.circleText}> {mount ? "in=true" : "in=false"}</p>
<p className={Style.circleText}> {state}</p>
</div>
</div>}
</Transition>
</div>
<div className={Style.circleMember}>
<p>enter=false</p>
<Transition in={mount} timeout={1000} enter={false} >
{(state) => <div className={Style.circleShape} style={transitionStyle[state]} >
<div>
<p className={Style.circleText}> {mount ? "in=true" : "in=false"}</p>
<p className={Style.circleText}> {state}</p>
</div>
</div>}
</Transition>
</div>
<div className={Style.circleMember}>
<p>exit=false</p>
<Transition in={mount} timeout={1000} exit={false} >
{(state) => <div className={Style.circleShape} style={transitionStyle[state]} >
<div>
<p className={Style.circleText}> {mount ? "in=true" : "in=false"}</p>
<p className={Style.circleText}> {state}</p>
</div>
</div>}
</Transition>
</div>
<div className={Style.circleMember}>
<p>nodeRef</p>
<Transition in={mount} timeout={1000} nodeRef >
{(state) => <div className={Style.circleShape} style={transitionStyle[state]} >
<div>
<p className={Style.circleText}> {mount ? "in=true" : "in=false"}</p>
<p className={Style.circleText}> {state}</p>
</div>
</div>}
</Transition>
</div>
</div>
</div>
);
}
export default OtherProps;
.circleGroup {
display: flex;
justify-content: space-between;
margin: 100px auto;
width: 1000px;
}
.circleMember {
text-align: center;
width: 100px;
}
.circleShape {
align-items: center;
background-color: red;
border-radius: 50px;
display: flex;
height: 100px;
justify-content: center;
width: 100px;
}
.circleText {
color: white;
}
button {
margin: 0 auto;
display: block;
}
import React from "react";
import OtherProps from "./components/otherProps/OtherProps";
function App() {
return (
<div className="App">
<ChainTransition />
</div>
);
}
export default App;
in、timeout以外のProps
Props | 未指定時(暗黙的に指定されている) | 未指定時から変更する場合 | 変更時内容 |
---|---|---|---|
enter | enter、enter={true} | enter={false} | enteringにならない |
exit | exit、exit={true} | exit={false} | exitingにならない |
mountOnEnter | mountOnEnter={false} | mountOnEnter、mountOnEnter={true} | 遅延マウント(初回のみ) |
unmountOnExit | unmountOnExit={false} | unmountOnExit、unmountOnExit={true} | exitedでアンマウント |
nodRef | nodeRef={false} | nodeRef、nodeRef={true} | entering、exitingにならない |
appear | appear={false} | appear={true} | appearの動作(in=trueと一緒に指定) |
appear
各コンポーネントのinの初期値をtrue、appear=trueにします。
変更箇所のみ
//mountの初期値をtrueにして、in=trueにする
const [mount, setMount] = useState(true);
//各コンポーネントにappear={true}を追加
<Transition in={mount} timeout={1000} {...callBacksNormal} appear={true} >
コールバック
状態の変化時に処理を行う事ができます。
//状態変化時のコールバック
const callBacks = {
onEnter: () => console.log("enterです"),
onEntered: () => console.("enteredです"),
onExit: () => console.log("exitです"),
onExited: () => console.log("exitedです"),
};
//Transitionに{...callBacks}を追加
<Transition in={mount} {...callBacks} timeout={1000} >
Transitionを連鎖させる
import React, { useState } from "react";
import { Transition } from "react-transition-group";
import Style from "./chaintransition.module.scss";
//アニメーションのスタイル4種類を定義(使わないものは省略可能)
const transitionStyle = {
entering: {
transition: "all 1s ease",
transform: "translateY(220px) ",
backgroundColor:"red"
},
entered: {
transition: "all 1s ease",
transform: "translateY(220px) ",
backgroundColor:"green"
},
exiting: {
transition: "all 1s ease",
transform: "translateY(0)",
backgroundColor: "blue",
},
exited: {
transition: "all 1s ease",
transform: "translateY(0)",
backgroundColor: "gray",
},
};
//ChainTransitionコンポーネント
const ChainTransition = () => {
//マウントの状態を管理
const [firstCircle, setFirstCircle] = useState(false);
const [secondCircle, setSecondCircle] = useState(false);
const [thirdCircle, setThirdCircle] = useState(false);
const [fourthCircle, setFourthCircle] = useState(false);
const [fifthCircle, setFifthCircle] = useState(true);
const [sixthCircle, setSixthCircle] = useState(true);
const [seventhCircle, setSeventhCircle] = useState(true);
//マウントのオンオフを切り替える
const changer = () => {
setFirstCircle(!firstCircle);
};
const callBacks = {
onEnter: () => {
setSecondCircle(true);
},
onEntering: () =>{
setThirdCircle(true);
},
onEntered: () => {
setFourthCircle(true);
},
onExit: () => {
setFifthCircle(false);
},
onExiting: () => {
setSixthCircle(false);
},
onExited: () => {
setSeventhCircle(false);
},
};
return (
<div className={Style.wrapper}>
<button onClick={changer}>inの切り替え</button>
<div className={Style.circleGroup}>
<div className={Style.circleMember}>
<p>(trigger)</p>
<Transition in={firstCircle} timeout={1000} {...callBacks}>
{(state) => <div className={Style.circleShape} style={transitionStyle[state]} >
<div>
<p className={Style.circleText}> {firstCircle ? "in=true" : "in=false"}</p>
<p className={Style.circleText}> {state}</p>
</div>
</div>}
</Transition>
</div>
<div className={Style.circleMember}>
<p>onEnter</p>
<Transition in={secondCircle} timeout={1000} >
{(state) => <div className={Style.circleShape} style={transitionStyle[state]} >
<div>
<p className={Style.circleText}> {secondCircle ? "in=true" : "in=false"}</p>
</div>
</div>}
</Transition>
</div>
<div className={Style.circleMember}>
<p>onEntering</p>
<Transition in={thirdCircle} timeout={1000}>
{(state) => <div className={Style.circleShape} style={transitionStyle[state]} >
<div>
<p className={Style.circleText}> {thirdCircle ? "in=true" : "in=false"}</p>
</div>
</div>}
</Transition>
</div>
<div className={Style.circleMember}>
<p>onEntered</p>
<Transition in={fourthCircle} timeout={1000} >
{(state) => <div className={Style.circleShape} style={transitionStyle[state]} >
<div>
<p className={Style.circleText}> {fourthCircle ? "in=true" : "in=false"}</p>
</div>
</div>}
</Transition>
</div>
<div className={Style.circleMember}>
<p>onExit</p>
<Transition in={fifthCircle} timeout={1000} >
{(state) => <div className={Style.circleShape} style={transitionStyle[state]} >
<div>
<p className={Style.circleText}> {fifthCircle ? "in=true" : "in=false"}</p>
</div>
</div>}
</Transition>
</div>
<div className={Style.circleMember}>
<p>onExiting</p>
<Transition in={fifthCircle} timeout={1000} >
{(state) => <div className={Style.circleShape} style={transitionStyle[state]} >
<div>
<p className={Style.circleText}> {sixthCircle ? "in=true" : "in=false"}</p>
</div>
</div>}
</Transition>
</div>
<div className={Style.circleMember}>
<p>onExited</p>
<Transition in={seventhCircle} timeout={1000} >
{(state) => <div className={Style.circleShape} style={transitionStyle[state]} >
<div>
<p className={Style.circleText}> {seventhCircle ? "in=true" : "in=false"}</p>
</div>
</div>}
</Transition>
</div>
</div>
</div>
);
}
export default ChainTransition;
.circleGroup {
display: flex;
justify-content: space-between;
margin: 100px auto;
width: 1000px;
}
.circleMember {
text-align: center;
width: 100px;
}
.circleShape {
align-items: center;
background-color: red;
border-radius: 50px;
color: red;
display: flex;
height: 100px;
justify-content: center;
width: 100px;
}
.circleText {
color: white;
}
button {
margin: 0 auto;
display: block;
}
import React from "react";
import ChainTransition from "./components/chainTransition/ChainTransition";
function App() {
return (
<div className="App">
<ChainTransition />
</div>
);
}
export default App;
コールバックのタイミング
nodeRef Propが渡されると、コールバックを使ってもnodeは渡されません。
コールバックの種類 | 適用タイミング |
---|---|
onEnter | entering適用前 |
onEntering | entering適用時 |
onEntered | entered適用時 |
onExit | exiting適用前 |
onExiting | exiting適用時 |
onExited | exited適用時 |
CSSTransition
Transitionとの大きな違いは、親要素の名前がCSSTransitionになっていることと、CSSTransition用のPropsとしてclassNameが使える事です。
import React, { useState } from "react";
import { CSSTransition } from "react-transition-group";
import Style from "./singleCSSTransition.module.scss";
//SingleCSSTransitionコンポーネント
const SingleCSSTransition = () => {
//マウントの状態を管理
const [mount, setMount] = useState(false);
//マウントのオンオフを切り替える
const changer = () => {
setMount(!mount);
};
return (
<div className={Style.wrapper}>
<button onClick={changer}>inの切り替え</button>
<div className={Style.circleGroup} >
<div className={Style.circleMember} >
<CSSTransition
in={mount}
timeout={1000}
classNames={{
appear:Style.testAppear,
appearActive:Style.testAppearActive,
appearDone:Style.testAppearDone,
enter:Style.testEnter,
enterActive: Style.testEnterActive,
enterDone:Style.testEnterDone,
exit:Style.testExit,
exitActive: Style.testExitActive,
exitDone: Style.testExitDone,
}}
>
{(state) =>
<div className={Style.circleShape} >
<div>
<p className={Style.circleText} > {mount ? "in=true" : "in=false"}</p>
<p className={Style.circleText} > {state}</p>
</div>
</div>}
</CSSTransition>
</div>
</div>
</div>
);
}
export default SingleCSSTransition;
.circleGroup {
display: flex;
justify-content: space-between;
margin: 100px auto;
width: 1000px;
}
.circleMember {
text-align: center;
width: 100px;
}
.circleShape {
align-items: center;
background-color: red;
border-radius: 50px;
display: flex;
height: 100px;
justify-content: center;
width: 100px;
}
.circleText {
color: white;
}
button {
margin: 0 auto;
display: block;
}
//apperaの最初のフレーム瞬間の状態
.testAppear {
transition: all 1s ease;
border-radius: 10px;
transform: rotateZ(30deg) scale(2);
opacity: 0;
}
//.testAppearの直後の状態
.testAppearActive {
}
//appearの最終フレームの状態
.testAppearDone {
transition: all 1s ease;
border-radius: 50px;
opacity: 1;
}
//enter中の最初のフレーム瞬間の状態
.testEnter {
transition: all 1s ease;
transform: translateY(0);
background-color: red;
}
//enter中の最終フレームの状態
.testEnterActive {
transition: all 1s ease;
transform: translateY(220px);
background-color: red;
}
//enter完了の状態
.testEnterDone {
transition: all 1s ease;
transform: translateY(220px);
background-color: green;
}
//exitの初期状態
.testExit {
transition: all 1s ease;
transform: translateY(220px);
background-color: green;
}
//exit中の最終フレームの状態
.testExitActive {
transition: all 1s ease;
transform: translateY(0);
background-color: blue;
}
//exit完了
.testExitDone {
transition: all 1s ease;
transform: translateY(0);
background-color: gray;
}
import React from "react";
import SingleCSSTransition from "./components/singleCSSTransition/SingleCSSTransition";
function App() {
return (
<div className="App">
<SingleCSSTransition />
</div>
);
}
export default App;
className
CSSTransitionのclassName Propsでは、各状態にクラス名を付けられます。
<CSSTransition
classNames={{
enter:Style.testEnter,
enterActive: Style.testEnterActive,
enterDone:Style.testEnterDone,
exit:Style.testExit,
exitActive: Style.testExitActive,
exitDone: Style.testExitDone,
}}>
今回はCSSModulesを採用しているので、使用する各状態用のクラスに個別に名前をつける必要がありますが、他の方法でCSSを指定している場合接頭辞を指定すれば、自動的にクラス名が生成されます。
※上記のように任意のクラス名に変更することも可能です。
接頭辞をtestにした場合
- | active | done |
---|---|---|
test-appear | test-appear-active | test-appear-done |
test-enter | test-enter-active | test-enter-done |
test-enter | test-exit-active | test-exit-done |
※appearクラスの追加タイミングを追ってみると、enterクラスと同時に適用されてしまうので使い方には注意が必要です。
appear
コンポーネントのinの初期値をtrue、appear=trueにします。
変更箇所のみ記載
//mountの初期値をtrueにしてin=trueにする
const [mount, setMount] = useState(true);
//CSSTransitonにappear={true}を追加
<CSSTransition
in={mount}
{...callBacks}
timeout={1000}
classNames={{
appear:Style.testAppear,
appearActive:Style.testAppearActive,
appearDone:Style.testAppearDone,
enter:Style.testEnter,
enterActive: Style.testEnterActive,
enterDone:Style.testEnterDone,
exit:Style.testExit,
exitActive: Style.testExitActive,
exitDone: Style.testExitDone,
}}
appear={true}
>
SwitchTransition
SwitchTransitionでTransition、CSSTransitionを囲みます。
SwitchTransition独自のPropsとしてmodeがあります。
import React, { useState } from "react";
import { SwitchTransition, Transition } from "react-transition-group";
import Style from "./transitionSwitching.module.scss";
//トランジションのスタイル4種類を定義(使わないものは省略可能)
const transitionStyle = {
entering: {
transition: "all 0.5s ease",
transform: "translateX(-150px) ",
backgroundColor:"red",
opacity:"0",
},
entered: {
transition: "all 0.5s ease",
transform: "translateX(150px)",
backgroundColor:"green",
opacity:"1",
},
exiting: {
transition: "all 0.5s ease",
transform: "translateX(-150px)",
backgroundColor: "blue",
opacity:"1",
},
exited: {
transition: "all 0.5s ease",
transform: "translateX(150px)",
backgroundColor: "gray",
opacity:"0",
},
};
const TransitionSwitch=()=> {
const [name, setName] = useState(false);
return (
<div>
<button onClick={() => setName(!name)}>Switching</button>
<div className={Style.squareWrapper}>
<SwitchTransition mode="in-out">
<Transition
key={name ? "aaa" : "bbb"}
timeout={500}
unmountOnExit
mountOnEnter
>
{state => <div state={state} style={transitionStyle[state]} className={Style.square}>
{name ? <p>AAA</p> : <p>BBB</p>}
</div>}
</Transition>
</SwitchTransition>
</div>
</div>
);
}
export default TransitionSwitch;
/* 簡易リセット */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* スクロールバー常時表示 */
html {
overflow-y: scroll;
}
.squareWrapper {
margin-top: 100px;
position: relative;
}
.square {
background-color: blue;
display: block;
width: 200px;
padding: 10px 20px;
margin: 0 auto;
color: #fff;
text-align: center;
position: absolute;
left: calc(50% - 100px);
}
button {
margin: 0 auto;
display: block;
}
import React from "react";
import TransitionSwitching from "./components/transitionSwitching/TransitionSwiching";
function App() {
return (
<div className="App">
<TransitionSwitching />
</div>
);
}
export default App;
mode
mode | 内容 |
---|---|
out-in | 先に現在の要素がアウトし、完了後に新しい要素がインする |
in-out | 先に新しい要素がインし、完了後に現在の要素がアウトする |
TransitionGroupとSwitchTransitionの使い分け
古い子要素のoutと新しい子要素のinを同時に行う場合は、TransitionGroupを使用します。
TransitionGroup
TransitionGroup は、Transition または CSSTransition のリストを管理する為のコンポーネントです。
TransitionGroupでTransitionやCSSTransitionをラップします。
各TransitionやCSSTransitionにはinは不要で、代わりにユニークなKeyを設定します。
import React, { useState,useRef } from "react";
import {TransitionGroup,Transition} from 'react-transition-group';
import Style from "./transitionList.module.scss";
//トランジションのスタイル4種類を定義(使わないものは省略可能)
const transitionStyle = {
entering: {
transition: "all 0.2s ease",
opacity:"0"
},
entered: {
transition: "all 0.2s ease",
opacity:"1"
},
exiting: {
transition: "all 0.2s ease",
opacity:"0"
},
exited: {
transition: "all 0.2s ease",
opacity:"0"
},
};
//TransitionGroupコンポーネント
const TransitionList = () => {
//inputに入力中の文字
const [inputting,setInputting]=useState("");
//input関連付け用のref
const inputRef =useRef();
//最後のID番号管理用
const [lastId,setLastId]=useState(3);
//初期アイテムリスト
const initialList=[
{id:0,word:"React"},
{id:1,word:"Hooks"},
{id:2,word:"Transition"},
];
//アイテムリストに初期アイテムをセット
const [items,setItems]=useState(initialList);
//アイテムの追加処理
const adder = () => {
if(inputting){
setItems(
items=>[
...items,
{id:lastId,
word:inputting}
]
)};
//IDのインクリメント
setLastId(prevId=>prevId+1);
//inputのクリア
setInputting("");
inputRef.current.value="";
};
//リセット
const reseter=()=>{
setInputting("");
inputRef.current.value="";
setItems(initialList);
}
//form送信防止
const stopSubmit=(e)=>{
e.preventDefault();
}
return (
<div className={Style.wrapper}>
<form action=""
onSubmit={stopSubmit}
className={Style.controller}
>
<div className={Style.controllerAddGroup}>
<input
className={Style.controllerInput}
ref={inputRef}
type="text"
onChange={
(e)=>setInputting(e.target.value)
}
/>
<button
className={Style.button}
onClick={adder}
disabled={!inputting ? true:false}
>追加</button>
</div>
<button
className={Style.button}
onClick={reseter}>
初期化
</button>
</form>
<div className={Style.cardGroup}>
<TransitionGroup className={Style.cardInner}>
{items.map(({id,word})=>(
<Transition key={id} timeout={200} >
{(state) =>
<div className={Style.cardShape} style={transitionStyle[state]} >
<button
className={Style.button}
onClick={() =>
setItems(items =>
items.filter(item => item.id !== id)
)
}
>
削除
</button>
<p className={Style.cardWord}> {word}</p>
<p className={Style.cardTransition}> {state}</p>
</div>}
</Transition>
)
)
}
</TransitionGroup>
</div>
</div>
);
}
export default TransitionList;
/* 簡易リセット */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* スクロールバー常時表示 */
html {
overflow-y: scroll;
}
@media screen and (min-width: 651px) {
.wrapper {
margin: 0 auto;
max-width: 600px;
}
.controller {
background-color: #ffebee;
border-radius: 4px;
display: flex;
filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.6));
justify-content: space-between;
margin-top: 40px;
padding: 20px;
}
.controllerAddGroup {
display: flex;
}
.controllerInput {
border: none;
padding: 10px;
background-color: #ffcdd2;
transition: all 0.5s ease;
&:focus {
background-color: #ffffff;
outline: none;
}
}
.button {
appearance: none;
background-color: #ec407a;
border: none;
color: #ffffff;
cursor: pointer;
font-family: inherit;
font-size: 1rem;
margin: 0;
outline: none;
padding: 10px 20px;
transition: all 0.5s ease;
width: 100px;
&:hover {
background-color: #ad1457;
}
&:disabled {
opacity: 0;
cursor: default;
}
}
.cardGroup {
display: flex;
justify-content: space-between;
margin-top: 40px;
max-width: 1000px;
}
.cardInner {
width: 100%;
}
.cardShape {
background-color: #e3f2fd;
border-radius: 4px;
display: flex;
filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.6));
justify-content: space-between;
padding: 20px;
max-width: 600px;
& + .cardShape {
margin-top: 20px;
}
}
.cardWord {
color: #212121;
display: inline;
font-size: 2rem;
font-weight: bold;
line-height: 1.2;
overflow: hidden;
padding: 0 20px;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
width: 400px;
}
.cardTransition {
background-color: #1e88e5;
color: #ffffff;
border-radius: 4px;
padding: 10px;
}
}
@media screen and (max-width: 650px) {
.wrapper {
margin: 0 auto;
max-width: 600px;
padding: 0 10px;
}
.controller {
background-color: #ffebee;
border-radius: 4px;
filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.6));
margin-top: 20px;
padding: 15px;
& > .button {
margin-left: auto;
margin-right: auto;
display: block;
margin-top: 10px;
}
}
.controllerAddGroup {
display: flex;
width: 100%;
justify-content: space-between;
}
.controllerInput {
border: none;
padding: 10px;
background-color: #ffcdd2;
transition: all 0.5s ease;
width: 100%;
&:focus {
background-color: #ffffff;
outline: none;
}
}
.button {
appearance: none;
background-color: #ec407a;
border: none;
color: #ffffff;
cursor: pointer;
font-family: inherit;
font-size: 0.5rem;
margin: 0;
outline: none;
padding: 10px;
transition: all 0.5s ease;
width: 100px;
&:hover {
background-color: #ad1457;
}
&:disabled {
opacity: 0;
cursor: default;
}
}
.cardGroup {
display: flex;
justify-content: space-between;
margin-top: 20px;
max-width: 1000px;
}
.cardInner {
width: 100%;
}
.cardShape {
background-color: #e3f2fd;
border-radius: 4px;
display: flex;
filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.6));
justify-content: space-between;
padding: 15px;
max-width: 600px;
& + .cardShape {
margin-top: 10px;
}
}
.cardWord {
color: #212121;
display: inline;
font-size: 1rem;
font-weight: bold;
line-height: 1.2;
overflow: hidden;
padding: 0 20px;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
width: 400px;
}
.cardTransition {
background-color: #1e88e5;
color: #ffffff;
border-radius: 4px;
padding: 5px;
}
}
import React from "react";
import TransitionList from "./components/transitionList/TransitionList";
function App() {
return (
<div className="App">
<TransitionList />
</div>
);
}
export default App;
リポジトリ