SVGアニメーションを簡単に実装できるSnap.svgの使い方
そもそもSnap.svgとは?
Snap.svgはSVGにアニメーションを付けることができるJavaScriptのライブラリです。
ベクター画像のパスを指定し動きを付けることで、CSSのみだと難しい複雑なアニメーションを実現することができます。
Snap.svgでアニメーションを実装する方法
今回は上のような、振り回されてるハンマーを作ってみます。
※ gif画像のため、多少カクついて見えるかもしれません。
1. jsファイルを読み込む
まず公式サイトからjsファイルをダウンロードします。
zipを解凍後、Snap.svg > dist > snap.svg-min.js
を任意のディレクトリに設置します。
<script type="text/javascript" src="snap.svg-min.js"></script>
あとはhead
タグ内でそれを読み込みます。
2. SVG画像を用意する
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-70 -50 400 300"> <rect x="60.63957" y="155.39365" width="189.22245" height="5" rx="2.5" transform="translate(-105.38649 -14.71627) rotate(-21.37376)" fill="#c69c6d" /> <path d="M105.05159,208.94745A28.47838,28.47838,0,0,1,80.09194,218.716Q69.30588,191.15637,58.5198,163.59674a22.33592,22.33592,0,0,1,24.95965-9.76849Q94.26553,181.38786,105.05159,208.94745Z" transform="translate(-58.5198 -82.15695)" fill="#3f4a57" /> <ellipse cx="71.20314" cy="159.23249" rx="13.40157" ry="5.02559" transform="translate(-111.6555 -45.25486) rotate(-21.374)" fill="#303a45" /></svg>
まず動かしたいSVG画像を用意します。
3. 動かしたいパスにクラスを指定
<svg id="js-svg" xmlns="http://www.w3.org/2000/svg" viewBox="-70 -50 400 300"> <rect id="svg-move-parts1" x="60.63957" y="155.39365" width="189.22245" height="5" rx="2.5" transform="translate(-105.38649 -14.71627) rotate(-21.37376)" fill="#c69c6d" /> <path id="svg-move-parts2" d="M105.05159,208.94745A28.47838,28.47838,0,0,1,80.09194,218.716Q69.30588,191.15637,58.5198,163.59674a22.33592,22.33592,0,0,1,24.95965-9.76849Q94.26553,181.38786,105.05159,208.94745Z" transform="translate(-58.5198 -82.15695)" fill="#3f4a57" /> <ellipse id="svg-move-parts3" cx="71.20314" cy="159.23249" rx="13.40157" ry="5.02559" transform="translate(-111.6555 -45.25486) rotate(-21.374)" fill="#303a45" /></svg>
今回の場合は全てのパスを動かしたいのでそれぞれにidを割り当てます。
4. 変形後のパスを用意する
<rect id="svg-move-parts1" x="60.63957" y="155.39365" width="189.22245" height="5" rx="2.5" transform="translate(-105.38649 -14.71627) rotate(-21.37376)" fill="#c69c6d"/><path id="svg-move-parts2" d="M105.05159,208.94745A28.47838,28.47838,0,0,1,80.09194,218.716Q69.30588,191.15637,58.5198,163.59674a22.33592,22.33592,0,0,1,24.95965-9.76849Q94.26553,181.38786,105.05159,208.94745Z" transform="translate(-58.5198 -82.15695)" fill="#3f4a57"/><ellipse id="svg-move-parts3" cx="71.20314" cy="159.23249" rx="13.40157" ry="5.02559" transform="translate(-111.6555 -45.25486) rotate(-21.374)" fill="#303a45"/>
変形後(アニメーション終了時)のタグを用意します。
x="60.63957" y="155.39365" width="189.22245" height="5" rx="2.5" transform="translate(-105.38649 -14.71627) rotate(-21.37376)" fill="#c69c6d"
そのタグから座標を抜き取ります。例だとこの部分ですね。
5. animateメソッドで動かす
const SPEED = 200; // アニメーションの速度const $svgParts1 = Snap("#svg-move-parts1");const $svgParts2 = Snap("#svg-move-parts2");const $svgParts3 = Snap("#svg-move-parts3");
$svgParts1.animate( { x: "75", y: "34", width: "189.22245", height: "5", rx: "2.5", transform: "translate(137.27166 -126.41038) rotate(77.23189)", }, SPEED, mina.easein);$svgParts2.animate( { d: "M156.4867,71.15524a28.47835,28.47835,0,0,1-5.92376-26.14034l57.72658-13.08162a22.33593,22.33593,0,0,1,5.92373,26.14033Z", }, SPEED, mina.easein);$svgParts3.animate( { cx: "246", cy: "15", rx: "5.02559", ry: "13.40157", transform: "translate(-148.11801 15.75089) rotate(-12.76835)", }, SPEED, mina.easein);
まずSnap
クラスのインスタンスを作成し、動かしたいタグそれぞれのidを指定します。
第2引数ではアニメーションの速度(ミリ秒)を、第3引数ではイージングを指定。
そしてそのインスタンスそれぞれで、先ほど抜き取ったパスを引数としてanimate
メソッドを呼びます。
色々ツッコミどころのある動きですが、これで簡単な2点間のアニメーションは実装できました。
今回のアニメーションは3点間を行き来することによってハンマーを振り回してるように見せるものなので、3つのパスを行ったり来たり&繰り返す必要があります。
6. アニメーションを繰り返す
$svgParts3.animate( { cx: "71.20314", cy: "159.23249", rx: "13.40157", ry: "5.02559", transform: "translate(-111.6555 -45.25486) rotate(-21.374)", }, SPEED, EASEIN, humerAnimation1);
上記ようにアニメーションが終了後に実行されるコールバック関数を引数で指定できるので、これを利用して3点間のアニメーションを繰り返します。
const EASEIN = mina.easein;let direction = true;
function humerAnimation0() { direction = true; $svgParts1.animate( { x: "60.63957", y: "155.39365", width: "189.22245", height: "5", rx: "2.5", transform: "translate(-105.38649 -14.71627) rotate(-21.37376)", }, SPEED, EASEIN ); $svgParts2.animate( { d: "M105.05159,208.94745A28.47838,28.47838,0,0,1,80.09194,218.716Q69.30588,191.15637,58.5198,163.59674a22.33592,22.33592,0,0,1,24.95965-9.76849Q94.26553,181.38786,105.05159,208.94745Z", }, SPEED, EASEIN ); $svgParts3.animate( { cx: "71.20314", cy: "159.23249", rx: "13.40157", ry: "5.02559", transform: "translate(-111.6555 -45.25486) rotate(-21.374)", }, SPEED, EASEIN, humerAnimation1 );}
function humerAnimation1() { if (direction) { $svgParts1.animate( { x: "75", y: "34", width: "189.22245", height: "5", rx: "2.5", transform: "translate(137.27166 -126.41038) rotate(77.23189)", }, SPEED, EASEIN ); $svgParts2.animate( { d: "M156.4867,71.15524a28.47835,28.47835,0,0,1-5.92376-26.14034l57.72658-13.08162a22.33593,22.33593,0,0,1,5.92373,26.14033Z", }, SPEED, EASEIN ); $svgParts3.animate( { cx: "246", cy: "15", rx: "5.02559", ry: "13.40157", transform: "translate(-148.11801 15.75089) rotate(-12.76835)", }, SPEED, EASEIN, humerAnimation2 ); } else { $svgParts1.animate( { x: "75", y: "34", width: "189.22245", height: "5", rx: "2.5", transform: "translate(137.27166 -126.41038) rotate(77.23189)", }, SPEED, EASEIN ); $svgParts3.animate( { cx: "246", cy: "15", rx: "5.02559", ry: "13.40157", transform: "translate(-148.11801 15.75089) rotate(-12.76835)", }, SPEED, EASEIN ); $svgParts2.animate( { d: "M156.4867,71.15524a28.47835,28.47835,0,0,1-5.92376-26.14034l57.72658-13.08162a22.33593,22.33593,0,0,1,5.92373,26.14033Z", }, SPEED, EASEIN, humerAnimation0 ); }}function humerAnimation2() { direction = false; $svgParts1.animate( { width: "189.22245", height: "5", rx: "2.5", transform: "translate(385 145) rotate(174.6146)", }, SPEED, EASEIN ); $svgParts2.animate( { d: "M329.75652,173.71985a28.47841,28.47841,0,0,1,26.68481-2.51572q2.77778,29.46448,5.55553,58.929a22.336,22.336,0,0,1-26.68481,2.51569Q332.53429,203.18431,329.75652,173.71985Z", }, SPEED, EASEIN ); $svgParts3.animate( { cx: "433", cy: "240", rx: "13.40157", ry: "5.02559", transform: "translate(-163.48199 -48.41855) rotate(-5.38564)", }, SPEED, EASEIN, humerAnimation1 );}humerAnimation1();
上記のようにdirection
でアニメーションの方向を保存し、アニメーションを繰り返します。
7. 速度などを調整
ハンマーが振り回されているように見せるため、2つ目のパスの透明度を下げて残像のようにします。
const $svgParts1 = Snap("#svg-move-parts1");const $svgParts2 = Snap("#svg-move-parts2");const $svgParts3 = Snap("#svg-move-parts3");const svgBody = $("#js-svg");const svgParts3 = $("#svg-move-parts3");const SPEED = 320;const EASEIN = mina.easein;let direction = true;let animationCount = 0;
function humerAnimation0() { direction = true; svgBody.css("opacity", "1"); $svgParts1.animate( { x: "60.63957", y: "155.39365", width: "189.22245", height: "5", rx: "2.5", transform: "translate(-105.38649 -14.71627) rotate(-21.37376)", }, SPEED, EASEIN ); $svgParts2.animate( { d: "M105.05159,208.94745A28.47838,28.47838,0,0,1,80.09194,218.716Q69.30588,191.15637,58.5198,163.59674a22.33592,22.33592,0,0,1,24.95965-9.76849Q94.26553,181.38786,105.05159,208.94745Z", }, SPEED, EASEIN ); $svgParts3.animate( { cx: "71.20314", cy: "159.23249", rx: "13.40157", ry: "5.02559", transform: "translate(-111.6555 -45.25486) rotate(-21.374)", }, SPEED, EASEIN, humerAnimation1 );}
function humerAnimation1() { let waitSecond = 200; // 最初の待つ時間 if (animationCount == 0) { waitSecond = 0; } if (direction) { setTimeout(function () { svgBody.css("opacity", ".1"); svgParts3.css("display", "none"); $svgParts1.animate( { x: "75", y: "34", width: "189.22245", height: "5", rx: "2.5", transform: "translate(137.27166 -126.41038) rotate(77.23189)", }, SPEED - 200, EASEIN ); $svgParts2.animate( { d: "M156.4867,71.15524a28.47835,28.47835,0,0,1-5.92376-26.14034l57.72658-13.08162a22.33593,22.33593,0,0,1,5.92373,26.14033Z", }, SPEED - 200, EASEIN ); $svgParts3.animate( { cx: "246", cy: "15", rx: "5.02559", ry: "13.40157", transform: "translate(-148.11801 15.75089) rotate(-12.76835)", }, SPEED - 200, EASEIN, humerAnimation2 ); }, waitSecond); } else { setTimeout(function () { svgBody.css("opacity", ".1"); svgParts3.css("display", "none"); $svgParts1.animate( { x: "75", y: "34", width: "189.22245", height: "5", rx: "2.5", transform: "translate(137.27166 -126.41038) rotate(77.23189)", }, SPEED - 200, EASEIN ); $svgParts2.animate( { d: "M156.4867,71.15524a28.47835,28.47835,0,0,1-5.92376-26.14034l57.72658-13.08162a22.33593,22.33593,0,0,1,5.92373,26.14033Z", }, SPEED - 200, EASEIN ); $svgParts3.animate( { cx: "246", cy: "15", rx: "5.02559", ry: "13.40157", transform: "translate(-148.11801 15.75089) rotate(-12.76835)", }, SPEED - 200, EASEIN, humerAnimation0 ); }, 100); }}function humerAnimation2() { direction = false; svgBody.css("opacity", "1"); $svgParts1.animate( { width: "189.22245", height: "5", rx: "2.5", transform: "translate(385 145) rotate(174.6146)", }, SPEED, EASEIN ); $svgParts2.animate( { d: "M329.75652,173.71985a28.47841,28.47841,0,0,1,26.68481-2.51572q2.77778,29.46448,5.55553,58.929a22.336,22.336,0,0,1-26.68481,2.51569Q332.53429,203.18431,329.75652,173.71985Z", }, SPEED, EASEIN, humerAnimation1 ); $svgParts3.animate( { cx: "433", cy: "240", rx: "13.40157", ry: "5.02559", transform: "translate(-163.48199 -48.41855) rotate(-5.38564)", }, SPEED, EASEIN, svgParts3UnVanish ); animationCount++;}
function svgParts3UnVanish() { svgParts3.css("display", "initial");}
//アニメーションスタートhumerAnimation1();
その他アニメーションのタイミングや速度などを変更すれば完成です 🎉