概要
みなさんこんにちはcandleです。今回はReactでGSAPを使ってみたいと思います。
簡単にアニメーションが使えるjsライブラリはjQueryが有名です。ただ、jQueryとReactは相性が悪いらしいです。また、React自体は私の知る限りは使いやすいアニメーションの機能がありません(Transitionはあるけど)。
しかし、古今東西webサービスを作った暁にはアニメーションが必要ではない方が珍しいです。
Reactでアニメーションを使おうとすると幾つもの候補があります。下のURLにはReactで使えそうなライブラリをいくつも紹介しています。
https://qiita.com/nabepon/items/c005a7d4491fd04b453e
その中でもGSAPはボリュームがあり、大きなライブラリです。もちろん、必要な機能だけをインポートして小さく使うことも可能です。早速挑戦してみましょう。
例によってこの記事もチュートリアル形式を採用しています。
前提
- Reactの知識がある
- create-react-appコマンドがインストールされている
準備
プロジェクトを作成します。ターミナルでコマンドを実行しましょう。
create-react-app react-animation cd react-animation
デザインを整えます。src/App.js
をこのようにします。
import React, { Component } from 'react' import './App.css' class App extends Component { render() { return ( <div className="App"> <header className="App-header"> <div>hello</div> </header> </div> ) } } export default App
続いてsrc/App.css
をこのようにしておきます。
.App { text-align: center; } .App-header { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); }
GSAPのインストール
gsapをreactで使うには2つのライブラリ、gsapとreact-gsap-enhancerが必要です。下のコマンドでインストールしましょう。
yarn add gsap react-gsap-enhancer
1度だけアニメーションを実行
1回だけアニメーションするコンポーネントなのか、それとも2回以上あるのかコードの書き方が変わります。
最初にコンポーネントがマウントされた時だけアニメーションが実行するコンポーネントを作ってみましょう。
src/components
を作成し、
mkdir src/components
OneTimeCardコンポーネントを作成します。
touch src/components/OneTimeCard.js
中身をこのようにします。
import React from 'react' import GSAP from 'react-gsap-enhancer' import { TimelineMax } from 'gsap' class OneTimeCard extends React.Component { constructor(props) { super(props) this.myCard = null } componentDidMount() { new TimelineMax().from(this.myCard, 1, { y: 20, opacity: 0 }) } render() { return ( <div> <div style={styles.box} ref={div => (this.myCard = div)}> <div>Card</div> </div> </div> ) } } const styles = { box: { width: '120px', height: '120px', zIndex: 1, cursor: 'pointer', border: '1px solid black', display: 'flex', justifyContent: 'center', alignItems: 'center', }, } export default GSAP()(OneTimeCard)
このコードの一番重要なところはここです。
componentDidMount() { new TimelineMax().from(this.myCard, 1, { y: 20, opacity: 0 }) }
TimeLineMaxを使ってref
参照先のDOMにアニメーションをつけています。
1秒で透明度0から1までy軸に20px動かして表示させてます。
src/App.js
を開いてこのようにします。
import React, { Component } from 'react' import OneTimeCard from './components/OneTimeCard' import './App.css' class App extends Component { constructor(props) { super(props) } render() { return ( <div className="App"> <header className="App-header"> <OneTimeCard /> </header> </div> ) } } export default App
サーバを起動して確認すると、下からふわっと現れてくるのがわかります。
yarn run start
2回以上動かすアニメーション
さて、今度はもう少し手の凝ったアニメーションコンポーネントを作ってみましょう。
ボタンを押すとアニメーションと共にカードが現れ、もう1度ボタンを押すと消えるコンポーネントを作ってみましょう。
新しくSwitchingCard.js
を作ります。
touch src/components/SwitchingCard.js
中身はこのようにします。
import React from 'react' import GSAP from 'react-gsap-enhancer' import { TimelineMax } from 'gsap' class SwitchingCard extends React.Component { constructor(props) { super(props) this.state = { isBox: false, } this.myElement = null this.myTween = new TimelineMax() } toggleBox() { if (!this.state.isBox) { this.addAnimation(this.createAnim.bind(this)) } else { this.addAnimation(this.createExitAnim.bind(this)) } this.setState(prevState => ({ isBox: !prevState.isBox, })) } createAnim() { return this.myTween .to(this.myElement, 0.1, { display: 'flex', clearProps: 'transform' }) .to(this.myElement, 1, { y: 10, opacity: 1 }) } createExitAnim() { return this.myTween .to(this.myElement, 1, { y: -1, opacity: 0 }) .to(this.myElement, 0, { display: 'none' }) } render() { const { isBox } = this.state return ( <div style={styles.wrapper}> <div onClick={this.toggleBox.bind(this)} style={styles.button}> Show </div> <div style={styles.box} ref={div => (this.myElement = div)}> <div>Card</div> </div> </div> ) } } const styles = { wrapper: { position: 'relative', }, button: { width: '110px', cursor: 'pointer', border: '1px solid black', padding: '5px 5px', }, box: { position: 'absolute', bottom: '55px', width: '120px', height: '120px', zIndex: 1, cursor: 'pointer', border: '1px solid black', display: 'none', justifyContent: 'center', alignItems: 'center', opacity: 0, }, } export default GSAP()(SwitchingCard)
src/App.js
を開いて、利用するコンポーネントを変更します。
import './App.css' import React, { Component } from 'react' import SwitchingCard from './components/SwitchingCard' class App extends Component { constructor(props) { super(props) } render() { return ( <div className="App"> <header className="App-header"> <SwitchingCard /> </header> </div> ) } } export default App
解説の前に、動かしてみましょう。
どうですか、ボタンを押すと、カードが表示され、もう一度押すと消えますね。
コードの解説をします。
アニメーション対象DOMはrefで管理
アニメーションの対象はReactのrefで行います。constructor()
で対象のDOMを代入する変数this.myElement = null
を初期化しています。
これは、JSX内で対象を定義します。
<div style={styles.box} ref={div => (this.myElement = div)}> <div>Card</div> </div>
ちなみに、React16.xで導入されたReact.createRef()
は使えませんでした。
アニメーションのインスタンスは使い回す
アニメーションを何度も記述する場合はその度にTimelineMax()
のインスタンスを作るのではなく、constructor()
中でthis.myTween = new TimelineMax()
インスタンスを作り再利用します。
これは、createAnim()
関数とcreateExitAnim()
関数の中で使われています。
createAnim() { return this.myTween .to(this.myElement, 0.1, { display: 'flex', clearProps: 'transform' }) .to(this.myElement, 1, { y: 10, opacity: 1 }) } createExitAnim() { return this.myTween.to(this.myElement, 1, { y: -1, opacity: 0 }).to(this.myElement, 0, { display: 'none' }) }
連続するアニメーションをGSAPに追加する
2回以上アニメーションが連続する場合はreact-gsap-enhancerのthis.addAnimation()
を使う必要があります。これはアニメーションがそれぞれで独立して動くのではなく前のアニメーションの動きを引き継ぐ必要があるからです。
この部分で作成したアニメーションがGSAPに追加されています。
if (!this.state.isBox) { this.addAnimation(this.createAnim.bind(this)) } else { this.addAnimation(this.createExitAnim.bind(this)) }
仮に、この部分を下のように変更して直接実行すると最初のcreateAnim()
のアニメーションとcreateExitAnim()
のアニメーションが独立して実行されて変になります。
if (!this.state.isBox) { this.createAnim() } else { this.createExitAnim() }
アニメーションが続く場合は必ずaddAnimation
しましょう。
まとめ
どうでしょうか、基本的なReactでのGSAPを使ったアニメーションを試してみました。GSAPは大変ボリュームがあるアニメーションライブラリなので、色々試して使ってみてください。
GSAP公式のGetting startedとreactの部分は一度読んでおくと良いでしょう。
Getting Started with GSAP
https://greensock.com/get-started-js
Getting Started: React and GSAP Animations