JavaScriptでSVGのpathをアニメーションする

以前どこかで見たSVGのパス(path)アニメーションがずーっと頭に残っていて、当時は作ってみたいと思っていたものの長い間放置していました。ふと、実装してみたくなったので、試行錯誤してみました。その結果が以下です。

See the Pen
SVG Path Hover Animation
by Shunichi Fujita (@funnydreamer)
on CodePen.

pathのアニメーション

TypeScriptで書いているので、馴染みがないと複雑に感じてしまうかもしれませんが、pathアニメーション自体はやっていることは単純です。SVGのpathは、次のような書き方で書かれているかと思います。

ちょっと簡単な例で見てみましょう。

<svg viewBox="0, 0, 100, 100">
    <path d="M 10 10, L 10 90, L 90 90, L 90 10, Z" fill="#009ce1" stroke="#000" />
</svg>

pathのd属性の中に、コマンド(描画指示)を書いていくことで図がかけるわけです。Mで座標の移動、Lで線を引く、Cで曲線を書くなどができます。詳しいコマンドはこちら。このようにして、描画の仕方と座標を指定して、図形などを書いていく感じですね。

時間経過でこのdの中の数値を変更していけばアニメーションします。

d=”M 10 10, L 10 90, L 90 90, L 90 10, Z”
d=”M 10 10, L 10 90, L 90 89, L 90 10, Z”
d=”M 10 10, L 10 90, L 90 88, L 90 10, Z”
・・・
d=”M 10 10, L 10 90, L 90 10, L 90 10, Z”

上記の例では、2つ目のLのY座標を90から10に変更していくわけですが、これは図形で見ると四角形が三角形になるまでの数値の流れになります。dの値は、setAttribute()で書き換えて行く形です。以下のように、時間経過で変更する値を変数にして更新していけばいいだけです。アニメーション部分だけ簡潔に記述すると以下のような感じですね。

function animate () {
  pathElement.setAttribute('d', "M 10 10, L 10 90, L 90 " + value + ", L 90 10, Z");
  value = value - 1;
  if ( value < 10 ) return;
  window.requestFrameAnimation(animate);
}
window.requestFrameAnimation(animate);

コマンドの更新で、直線の制御は楽なのですが、今回のコードでは直線から曲線に変形するので、流れが綺麗にならず少し苦労しました。曲線コマンドの数値の特性などを理解しておかないと、思い通りの動きが難しいかもしれませんので、SVGエディターなどで色々書いてみるのが良いと思います。

ホバーイベントを設定する

pathが動かせるようになりました。なんでもそうですが、Webで動かせるようになるとインタラクションにしたくなります。ということで、ホバーイベントを設定します。ホバーしている時に、pathを変形して、ホバーを外したらpathを戻すみたいな形にするのですが、CSSのtransitionのように簡単にはいけません。

ひとまず、pathの変形前の初期値と、変形後の最終値を用意して、ホバー中は、最終値に向かってアニメーションしていき、最終値になった時はpathの変形を止めます。ホバーを外すと、初期値に向かってアニメーションしていき、初期値になった時はpathの変形を止めます。

See the Pen
dypWpxM
by Shunichi Fujita (@funnydreamer)
on CodePen.

イージングを設定する

動きはしたんですが、なんか味気ないですね。さきほどのコードは四角⇄三角の変形の点の動きが等速直線運動になっています。等速直線運動だとなんだか気持ち良くないですよね。等速直線運動は自然界にはあまりない動きなので不自然に感じてしまいます。そんな時はイージングを設定します。CSSのtransitionやanimationのease-inとかease-in-outとかがイージングです。イージングの違いを理解するために、CSSのイージングを触ってみましょう。下のグレーの枠をホバーしてみてください。それぞれの違いが、分かるかと思います。

See the Pen
NWRjbBo
by Shunichi Fujita (@funnydreamer)
on CodePen.

なんとなくイージングというものが感覚的につかめたでしょうか?

先ほどの例にイージングを追加してみます。イージングに必要なのが、アニメーションの経過時間や、アニメーションの開始から終了までの時間です。開始から終了までの時間は、cssでいうところのdurationです。durationを3sで設定すると、3秒でアニメーションが完了しますね。今回は、600ミリ秒(0.6秒)でアニメーションを完了するという設定にして、実装しました。

イージングの式はhttps://easings.net/jaのeaseOutQuadを使っています。(サイトのJSの数式は下の式と微妙に違いますが、数式を展開すると、同じになります。)

1 - (1 - x) * (1 - x)

1 - (1 - 2x + x*x)

1 - 1 + 2x - x*x

2x - x*x

x * ( 2 - x )

xは、アニメーションの進捗率になります。例えば、durationが600ミリとなっている場合は、アニメーションが、開始から300ミリ秒の時にx=0.5になり600ミリ秒の時にx=1になります。

See the Pen
easing
by Shunichi Fujita (@funnydreamer)
on CodePen.

上記の例では、ホバー時には前進フラグを立て、ホバーを外した時には、後退フラグを立て、アニメーションの方向を決めます。startはアニメーションが開始した時間です。requestAnimationFrameでコールバックにしていした関数(ここではanimate)は、DOMHighResTimeStampを引数を受け取りますが、ここではtimestampがそれです。これによって、timestamp - startで経過時間を取得することができます。

進捗率(Process)は、

process = ( timestamp -  start ) / duration

で表します。

進捗率がでたので、イージングの値が求められます。

//前進の時
value = startValue + diff * easeOut(process);
//後退の時
value = endValue - diff * easeOut(process);

diffは、開始位置(startValue)と終了位置(endValue)の差です。前進の時には、進捗率が増えるにつれ終点に近くわけですが、その近づき方を等速ではなくeaseOutを通すことで後半がゆっくりになるように調整しています。

//等速の時はprocessをそのまま使います
value = startValue + diff * process;

このようにして、イージングの式を変えることで、いろんな動きを表現できます。

さて、こんな感じで、SVGのパスのアニメーションをすることができました。イージングなどを自分で実装しないとならないのが、少し面倒ですが、パスアニメーションができると表現の幅が広がりますので、是非、実装してみてください。

funnydreamer
栃木生まれのミドルエイジ。フロントエンドとデザインの領域におりましたが、最近はマーケティングやライティングにPythonによる自動化など何でも屋になってきました。趣味は、ゲーム、アニメ、自転車(ポタリング)、カフェ巡り、お絵描きと自称多趣味。ケーキはショートケーキが好物。

ADD COMMENT

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください