くるみ
- 「SVG画像を使って簡単なアニメーションを実装したい」
- 「なんかそういうライブラリないん?」
という方に向けてSVG画像のアニメーションを簡単に実装できる「Snap.svg」の使い方をまとめました。
参考になれば幸いです⸝⸝- ̫ -⸝⸝
お品書き
そもそもSnap.svgとは?
Snap.svgはSVGにアニメーションを付けることができるライブラリです。
ベクター画像のパスを指定することでお手軽に動きを付けることができる優れもの。
SVG画像を動かす手順
完成イメージ
今回は本サイトのローディング画面のアニメーションにて振り回されてるハンマーの部分を作ってみます。
上のはgif画像なので少しカクついてますが、リアルなものが見たい場合はローディング画面の方を確認してみてください。
手順
基本的にはこれだけ。簡単にできちゃいます。
くるみ
Snap.svgでアニメーションを実装する方法
jsファイルを読み込む
まず公式サイトからjsファイルをダウンロードします。
zipを解凍したらSnap.svg>dist>snap.svg-min.jsを設置し、
<script type="text/javascript" src="snap.svg-min.js"></script>
で読み込んであげれば準備完了です。
SVG画像を用意する
<svg id="js-svg" data-name="ハンマー" 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画像を配置します。
動かしたいパスにクラスを指定
<svg id="js-svg" data-name="ハンマー" 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を割り当てます。
変形後のパスを用意する
//変形後
<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"
この部分ですね。
animateメソッドで動かす
let $svgParts1 = Snap("#svg-move-parts1");
let $svgParts2 = Snap("#svg-move-parts2");
let $svgParts3 = Snap("#svg-move-parts3");
let SPEED = 200; //アニメーションの速度,
$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を指定してあげればおけ。
第二引数ではアニメーションの速度(ミリ秒)を、第三引数ではイージングを指定してあげます。
そしてそのインスタンスそれぞれで、先ほど抜き取ったパスを引数としてanimateメソッドを呼んであげれば…
動いちゃいます。
色々ツッコミどころのある動きですが、これで簡単な2点間のアニメーションは実装できました。
今回のアニメーションは3点間を行き来することによってハンマーを振り回してるように見せるものなので、3つのパスを行ったり来たり&繰り返す必要があります。
アニメーションを繰り返す
$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点間のアニメーションを繰り返します。
let direction = true;
let EASEIN = mina.easein;
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();
このように関数を呼び合うことでアニメーションを繰り返します。
ブログ主
速度などを調整して完成
ハンマーが振り回されているように見せるため、2つ目のパスの透明度を下げて残像のようにします。
let $svgParts1 = Snap("#svg-move-parts1");
let $svgParts2 = Snap("#svg-move-parts2");
let $svgParts3 = Snap("#svg-move-parts3");
let svgBody = $("#js-svg");
let svgParts3 = $("#svg-move-parts3");
let SPEED = 320;
let EASEIN = mina.easein;
let direction = true; //アニメーションの向き(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();
その他アニメーションのタイミングや速度などを変更してあげれば…
完成です。
くるみ
まとめ
今回はほんの一部のしか利用してなく、このライブラリではまだまだ色んなことができます。詳しくは公式のドキュメントからどうぞ。
参考になれば幸いです!では⸝⸝- ̫ -⸝⸝