Nuxt.jsで自己紹介サイトを作りました。
https://nitta.studio/
見ていただくと分かる通りアニメーションをしまくったのですが、、
https://t.co/CXj31medDj
— 新田聡一郎 (@soichiro_nitta) 2018年4月26日
Nuxt.jsで自己紹介サイト作りました。NetlifyホスティングでPWA対応してます。
いろいろ自分のイカれた略歴など晒しました。宜しくお願いします。
VuexとVue.jsのウォッチャをつかって、
- イベントハンドリング
- ステート変更
- ウォッチャで検知
- 複数のコンポーネントでアニメーション発火🔥
のような書き方をしたら最高だったので、ご紹介です。
アニメーションって、どこにどの処理書けばいいのか困りませんか?
凝ったものを実装するとめちゃめちゃなコードになりがちですよね...
しかーし!Vue.jsのデータ駆動
とよばれる設計のおかげで、アニメーションの制御がとっても綺麗にできます。
(※データ駆動とは、データを中心にDOM描画をおこなったり、アクションを起こしたり、振る舞いを変えることができる設計のこと)
インタラクションが得意なデザイナ・フロントエンドさんには超おすすめですよ!
必要となる前提知識
- Nuxt.js
- Vue.js
- Vuex
- TweenMax
筆者はいつもPugとStylusを使いますが、
今回はみんな大好きHTML
とSCSS
でやってみますね!
てかどう考えてもPug
とStylus
が一番いいだろ〜何でみんな嫌がるの全然意味がわからない😩
やること
Nitta.Studioのアニメーションは、解説の題材にしてはちょっと複雑なので、
簡単なサンプルを用意してみました。まずは完成形から。
- ボタンをクリック(イベントハンドリング)
- Vuexでストアのステートを変更
-
TheLeft
,TheCenter
,TheRight
,TheBackground
コンポーネントのウォッチャで検知 - 各コンポーネントでアニメーション🔥
と、このような流れになっています。今回は順にポイントを確認していこうと思います。
サンプルコードは以下から。
https://github.com/soichiro-nitta/qiita-animation
各自cloneするなどして、
$ npm install # Or yarn install
$ npm run dev # Or yarn dev
して開発環境を立ち上げてみましょう。
それでは解説していきまーす! Qiita初投稿でテンションあがってるぜフォォォ!
イベントハンドリング
まず、components/TheButton.vue
を見ていきましょう。
ここでは、クリックイベントをトリガーに、ステートの変更をおこないます。
(Vuexのストアの状態を変更するのでミューテーションをコミットしています。)
<template>
<div class='TheButton'>
<div
class='TheButton_Start'
@click='click' <!-- メソッドを指定 -->
>
↑ click
イベントをバインディング。
<script>
import {mapGetters, mapMutations} from 'vuex'
export default {
// ...
methods: {
// ...
...mapMutations({
click: 'click' // `this.click()`にマッピングされます
})
}
}
</script>
↑ mapMutations
ヘルパーでミューテーションをマッピング。
これでクリック時にストアのミューテーションが実行されますね。
ここではclickイベント
にミューテーションをバインドするだけです。
ここにアニメーションのコードは書きません。
ストア
ストアはstore/index.js
で定義しています。
export const state = () => ({
entered: false // このステートが変更されるとアニメーションが🔥
})
export const getters = {
entered: state => state.entered // 各コンポーネントのウォッチャで監視するので
}
export const mutations = {
click (state) {
state.entered = !state.entered // クリックされたらステートを切り替えます
}
}
↑ タイプclick
のミューテーションは、state.entered
をトグルする形になっています。
これでクリックされたら、state.entered
が切り替わるようになりますね。
あとで説明しますが、各コンポーネントのウォッチャでは、
state.entered
が「true
になったとき」と「false
になったとき」で書き分けをしていきます。
state.entered
はウォッチャで監視するので、ゲッターも定義しておきます。
ウォッチャ
ここからが本番!
各コンポーネントのウォッチャでステートの変更を検知して、アニメーションを発火します🔥
ここではTheLeft
コンポーネントのウォッチャ(watch
オプション)を見てみましょう。
<script>
import {mapGetters} from 'vuex'
import {TweenMax, Expo, Elastic} from 'gsap'
export default {
// ...
watch: {
entered (val) { // ステートの`entered`が切り替わるたび、この処理が実行される
this.flash() // アニメーション🔥
val ? this.enter() : this.leave() // `entered`の値によってアニメーションを書き分け🔥
}
},
methods: { // アニメーションの宣言はここ
flash () {
requestAnimationFrame(() => {
TweenMax.to(this.$refs.title, 0.05, { // `this.$refs`でDOMにアクセス
color: 'red',
scale: 1.3,
ease: Expo.easeIn,
repeat: 19,
yoyo: true
})
})
},
enter () { // `entered`が`true`になったとき発火
requestAnimationFrame(() => {
TweenMax.to(this.$refs.background, 1, {
scaleX: 1,
ease: Expo.easeOut
})
})
},
leave () { // `entered`が`false`になったとき発火
requestAnimationFrame(() => {
TweenMax.to(this.$refs.background, 1, {
scaleX: 0,
ease: Expo.easeOut
})
})
}
}
}
</script>
↑ どうでしょう。綺麗じゃないですか?
ストアのentered
が切り替わるたびに、watch
オプションで宣言した処理が実行されます。
筆者のおすすめは、
watch
オプションにはロジックだけ書くようにして、
実際のアニメーションのコードはmethods
オプションに定義する形です。
ウォッチャに書くのはシンプルなロジックだけ!と決めておくと、汚れなくてよいと思います。
他のTheCenter
, TheRight
, TheBackground
コンポーネントも同様の流れです。
要は、動かしたい対象のDOMがあるコンポーネントに、そのアニメーションも書くという設計です。
どのアニメーションがどこにあるのか、わかりやすくてイイですね!
これで今回のように、イベントハンドリングするDOMと、アニメーションしたいDOMが別コンポーネントでも、
Vuexとウォッチャのおかげで役割をハッキリさせることができました。
おまけ
async/awaitを使ったsetTimeout()
の筆者オレオレmixinをご紹介。
まずは通常のsetTimeout()
、こんな感じですよね?
<script>
export default {
// ...
watch: {
isHell () {
setTimeout(() => {
this.hell1()
setTimeout(() => {
this.hell2()
setTimeout(() => {
this.hell3()
setTimeout(() => {
this.hell4()
setTimeout(() => {
this.hell5()
}, 500)
}, 400)
}, 300)
}, 200)
}, 100)
}
},
// ...
}
</script>
ここは地獄か😇?
次に、mixinを使用したコードを見てみましょう。
<script>
export default {
// ...
watch: {
async isHeaven () {
await this.$delay(100)
this.heaven1()
await this.$delay(200)
this.heaven2()
await this.$delay(300)
this.heaven3()
await this.$delay(400)
this.heaven4()
await this.$delay(500)
this.heaven5()
}
},
// ...
}
</script>
神じゃないですか?😻
plugins/mixin.js
にメソッドの詳細がありますので、見てみましょう。
import Vue from 'vue'
Vue.mixin({
methods: {
$delay (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
}
})
どうですか? これでコールバック地獄卒業しましょう。
(今回紹介しませんでしたが、TheCenter
, TheRight
, TheBackground
コンポーネントでも使用しています。)
おわりに
いかがでしたでしょうか?
筆者は他のJSフレームワークも経験がありますが、
イベントハンドリング → アニメーション発火の設計には悩む場面が多く、
アニメーションどこに定義すべきか、DOMへのアクセスどうしよう、と試行錯誤の日々でした。
結局、自分ルールを作っても、できたアニメーションのコードはひどく読みづらいものでした。
しかしVue.jsを試してみると、Vuexとウォッチャのおかげで、
ロジックと、実際のアニメーションのコードを、スッキリと分けることができました!
当面はこの形に落ち着きそうだなと思っています。
また、今回はアニメーション制御の解説でしたが、
Vue初心者には、イベントハンドリングやウォッチャの使い方、Vuex、メソッド定義の設計など、いい感じに学べて、かつ視覚的にも理解がすすみそうなので、入門によい題材なのではと思います。
Vue.jsはインタラクションが得意なデザイナ・フロントエンドさんには本当にオススメなので、
ぜひ題材にしていただいて、Vue使いデザイナー・フロントエンドが増えてくれれば筆者は嬉しいです〜😸
(またTheButton
コンポーネントでは、今回トランジションを使用しましたが解説を省略しています。Vue.jsのアニメーションの真髄は、まだまだいっぱいあります。もしご興味のある方は公式ガイドでわかりやすく記されていますので、ご覧になられてはいかがでしょうか。)