Vue で Web Animations をかっこよく実装するライブラリを作ってみた

TOC

  1. 現状で Vue.js で Web Animations を使う場合
  2. 「動作」で書くか「状態」で書くか
  3. 「状態」で Web Animations を記述する。
  4. 今後の課題
  5. 余談:実装がスッカスカな話

こんにちは。shundroid です。
あけましておめでとうございます(遅)
10か月振りくらいの投稿ですね。

高校が休みになっているので、勉強・開発に励んでおりました。

そこで、Vue.js で Transition だけでなく、
Web Animations を取り入れたい状況が出てきました。

現状で Vue.js で Web Animations を使う場合

そこで次のようなコードを書いたのですが、
これは Vue.js としてはふさわしくないですよね。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<template>
<div id="app">
<div id="rect" ref="rect" />
</div>
{{ active ? 'Animating' : 'Stopping' }}
<button @click="active = !active">Toggle</button>
</div>
</template>

<script>
export default {
data() {
return {
active: false
}
},
watch() {
active() {
if (this.active) {
this.$refs['rect'].animate([
{ transform: 'translate(0,0)', offset: 0 },
{ transform: 'translate(100px,0)', offset: 0.25 },
{ transform: 'translate(50px,0)', offset: 0.5 },
{ transform: 'translate(120px,0)', offset: 1 }
], {
duration: 1000,
easing: 'ease'
})
}
}
}
}
</script>

<style>
#rect {
width: 100px;
height: 100px;
background-color: red;
}
</style>

どこがふさわしくないのでしょうか。 ref を使っている辺りが明らかに良くないですけど、
問題はそれ以上にあると考えます。

「動作」で書くか「状態」で書くか

事を大きくしすぎた気もしますが、根本的な問題はここにあると思います。
アニメーションのような 一過性 のものは、一般的には「動作」で書くのがふさわしいですよね。
そのため、 .animate という メソッド によって「動作」を記述しているのです。

ただし、Vue.js は伝統的に、「動作」による記述よりも「状態」による記述を好みます。
例えば、View上の文字を変更するのに element.updateText('hoge!') みたいに書いたりはしませんよね。
{{ text }} のような text という data の「状態」の記述によって、UIの変更を行っています。
インラインテキストだけでなく、属性やスタイルの変更、また Transition についても同様に、「状態」による記述を行っています。

これはどうしてでしょうか。Vue.js の存在意義を考えるとこれは明らかです。
先ほども少し触れましたが、「動作」というのは一過的な概念です。
つまり、一回「動作」してしまえば、それが長い間ずっと続くようなことはありません。
ただし、Vue にとっては、これが長い間続いてほしいんですよね。これはなぜか。
まさしく、双方向データバインディング を保つためです。
データのリンクは、動作のように一回一回の時のみかろうじてつなぎ留められているようではいけないのです。

このように Vue で「状態」が重んじられている背景を踏まえると、そこに「動作」で記述されたプログラムを書くのは、
なんだか異物感を感じるわけです。もちろん動きはするんですけど、ちょっと消化不良というか、そういう感覚です。

HTML(XML) は非常に「状態」を書くのに優れた(マークアップ)言語です。
対してJSは「動作」を書くのを主としているわけなので、
例え .animate メソッドの引数で渡されているオブジェクトが状態だ!と主張したとしても、
<template> タグ内で書けた方がよいですよね。

というわけで、Web Animation を「状態」で記述するようにしたのが、このライブラリです。
コードを見てみましょう。

※ もちろん Vue でも動作を好む場面はあります。 v-on などに代表されますが、これらで実行される処理は View の背後の、ロジック的な部分であって、Vue でなくても別に良い部分なわけなので、ここをとって「Vueは動作による記述を好む」というのは言いすぎな気がします。

「状態」で Web Animations を記述する。

インストールは、yarn または npm で行ってください。

1
$ yarn add vue-animate-component

または

1
$ npm i --save vue-animate-component

コードは次のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template>
<div id="app">
<div id="rect">
<animation v-model="active" :duration="1000" easing="ease">
<keyframe transform="translate(0, 0)" :offset="0" />
<keyframe transform="translate(100px, 0)" :offset="0.25" />
<keyframe transform="translate(50px, 0)" :offset="0.5" />
<keyframe transform="translate(120px, 0)" :offset="1" />
</animation>
</div>
{{ active ? 'Animating' : 'Stopping' }}
<button @click="active = !active">Toggle</button>
</div>
</template>

<script>
import { Animation, Keyframe } from 'vue-animate-component'
export default {
components: {
Animation,
Keyframe
},
data() {
return {
active: false
}
}
}
</script>

<style>
#rect {
width: 100px;
height: 100px;
background-color: red;
}
</style>

Web Animation をタグによって記述できます。 v-modeltrue になった時、

アニメーションの終了は v-modelfalse になったことを検知してください。今後 @finish などを実装するかもしれませんが。

詳細な利用法は、Github レポジトリをご覧ください。

今後の課題

  • v-modelfalse になった時、アニメーションを pause する
  • @finish などのイベントを実装する(「動作」による記述を奨励するようですが…)

余談:実装がスッカスカな話

この実装は Github で見られるんですけど、スッカスカなので試しに keyframes.vue を見てみましょう

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div></div>
</template>

<script>
export default { }
</script>

<style scoped>
div {
display: none;
}
</style>

えっ()

つまりそういうことなんです。察してください。
animation.vue のほうでは、自分と子要素の $attrs をそのまま .animate の引数に丸投げしているだけです。

そんな意味あることなの?と思われるかもしれませんが、大事なのはその変換過程ではなく、
状態は <template> 内で書く、という役割分担がはっきりすることと、
またアニメーションの開始を v-model による状態変化で行えることであってほしいです(希望)