JavaScript
HTML5
CSS3
SVG
Snap.svg.js
LIGincDay 13

SVG奮闘記 ー SVG要素をぐにょぐにょ動かす実装を完了させるまでに右往左往した話

More than 3 years have passed since last update.

こんにちは。せいと(@seito2014)です。

LIGアドベントカレンダー13日目です。


はじめに

最近とあるサイト制作でSVGアニメーションを利用したのですが、そこでつまづきまくったことをつらつらと書いていきたいと思います。

とあるサイトというのはこちらで、見ていただくとわかるのですが、メインビジュアルがぐにょんぐにょん動いてます。

他にもいたるところでSVGやらCanvasやら使ってるサイトなのですが、今回はメインビジュアルのアニメーションに言及して書いていこうと思います。

・イメージ図

flame-sample.png

※キャプチャ画像はちょっとグレーなので別途画像を用意しました。

当時、デザイナーさんから静止画のPSDをもらって「この部分をこう、ぐにょぐにょ動かしてほしい」と言われて、やるならCanvasかSVGのどちらかだろうなあと思いました。

ただ、Canvasでやる場合は恐らく計算式が大変なことになるんじゃ、と思ってSVGでチャレンジしてみることに。


1.まずはSVG要素をぐにょぐにょ動かす


方法1 filter(CSS3) + animation(CSS3)

まずはどうすればSVG要素をぐにょぐにょ動かせるのか、と思ってぐぐってみるとCSS-TRICKSさんの記事にたどり着きました。

ここで紹介されているGooey effectなるメニューがイメージに近い動きだと思ったので、まずこれを使ってぐにょぐにょ動かそうと思ったわけです。

しかもJSいらずでCSSのfilterプロパティと組み合わせるだけでぐにょれるので、これ+CSS3のanimation合わせればいけるんじゃないかと。


結果

だめでした。orz

このfilterプロパティ、手頃なのはいいのですが、Safariなど多くのブラウザで対応しておらず、今の段階では使えないなーということに。

ちなみにCodropsさんのチュートリアルも参考にしました。

こちらではTweenMaxを使って実装しているようでしたが、結局filterを使っているようだったので、やはりクロスブラウザ対応できず。


方法2 Snap.svg.js

次に試したのがSnap.svg.jsです。

簡単にいうとこれはSVGを動かすJSライブラリで、Adobeが開発元だったりするのでけっこう有名です。

文法がjQueryに似ているので使い方も簡単!

簡単にパスを動かすだけのアニメーションなら以下の書き方でいけます。

Snap( '#svg path' ).animate({d: pathの値 }, 時間 );

ただ、これを使う前にまず素材となるSVG画像が必要だったので、まずはイラレで図形を作成しました。

1.まずIllsutrationSVG画像を2枚(アニメーション前とアニメーション後)をイラレで描く。(ペンツールで図形のパスを書く)

2.その後、「ファイル→別名で保存→ファイル形式:SVG→保存→SVGオプション→SVGコード」で出てきたHTMLをコピペ。

そして出来たデモがこちら。

http://codepen.io/seito2014/pen/WrQjER

<svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"

y="0px" viewBox="0 0 780.8 334" enable-background="new 0 0 780.8 334" xml:space="preserve">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#ccc" d="M25.4,54.6c0,0,18.5-55.6,153.5-33.8s350.6-23.9,417-20.6
s227.6,27.2,174.2,129.6s-22.9,137.2-33.8,163.3s-61,44.6-117.6,40.3c-56.6-4.4-223.2-51.2-291.8-51.2s-220,44.6-247.2,30.5
c-27.2-14.2-54.7-36-74.2-78.4S23.2,69.9,25.4,54.6z"
/>
</svg>

var $svg = Snap( '#svg path' ); //動かしたいpath要素

var path = 'M22,62c0,0,68-52,156-46s100,34,196,34S512,7,566,15.5S740,18,740,102s-58,152-28,182s-217.2,52-321.6,0S110,356,52,306s8-920-152S8,142,22,62z'; //アニメーション後のパス

$svg.animate({d: path }, 3000 );
//要素.animate({d: 移動後のパス }, 'アニメーション時間' );


結果

いい感じです!

今回の場合、アニメーション前とアニメーション後のSVGパスで、 パスの数を同じにしておく ことが大事でした。

パスが同じ数だと形状を維持したまま動くんですが、異なるとかなりダイナミックに変形(というか崩壊)しつつアニメーションしてしまいました。


2.マスクをかける

さて、ぐにょぐにょ動いたのはいいのですが、それだけではダメです。

今回のケースでは後ろに動画や画像が配置されており、SVGでぐにょっているのはあくまで周りのフレーム部分だけです。

次はそれをどう実現するか。


方法1 clip-path

SVGでマスクをかける、要素を切り抜く、といった表現をする場合、CSSのclip-pathでそれが実現できます。参考サイト

それを知っていた僕は、↑で実装したデモにこれを合わせて実現しようとしたのですが、、、


結果

だめでした。orz

こちらも一部のブラウザでしか有効ではない模様。

filterが使えない時点で気づけよ、って話なんですけどね。。。


方法2 SVG画像たちのパスの位置を合わせる

「clip-pathが使えないんじゃ、普通に動画、画像要素の上にSVGを乗っけるか」という発想に至りました。

つまり、用意して動かすべきSVG画像は、先ほどつくったこれ↓

path1.png

ではなく、

path2.png

こっちの枠の部分を作って動かす、ということです。


結果

うまくいきました!

http://codepen.io/seito2014/pen/waXvmv

このあとちょっとした微調整や仕様が若干変わったりしたために、都度イラレ開いてSVG画像をつくることになって面倒でしたが、まあ動いたので結果オーライ。


3.ループさせる

最後に、アニメーションは無限に続けたいのでループするようにしました。

http://codepen.io/seito2014/pen/KVdmvq?editors=001

var $svg = Snap( '#svg path' );

var path = [
'M0,0v680h688V0H0z M643.6,217.6c6.4,164.9-3.7,190.1-6.1,311.6c0.1,85.5-24.6,79.7-47.6,76.8c-4-0.5-22.3-0.3-49.7,0.3c-35.6-12-115.2-35.3-195.1-35.3c-84.7,0-169.1,26.2-201,37.3c-46.5-2.3-78.5-12.7-85-13.8c-28.2-47.4-0.5-127.3-13.1-212c-8.1-54.3-9.2-153.5,0-212c10.5-66.6,16.4-66.7,33-89.6c10.3-14.1,93.7-6.7,125.2,2.8c64.9,21.7,110.1,11.5,143.6-8.5c71.7-42.9,220.5-13.3,256.6-7.1C649.1,70.7,641,150.5,643.6,217.6z', //パスその1
'M0,0v680h688V0H0z M643.6,217.6c-6.9,65.5-8.4,226.2-6.1,311.6c2.3,85.4-24.6,79.7-47.6,76.8c-4-0.5-22.3-0.3-49.7,0.3c-35.6-12-115.2-35.3-195.1-35.3c-84.7,0-169.1,26.2-201,37.3c-46.5-2.3-79.2-6.5-85-13.8c-23.8-29.9-16.9-180.1-13.1-212c3.5-28.9,5.2-132.8,0-212C40.8,92,57.8,87.5,79,81.1c13.4-4.1,99.9,8.9,125.2,2.8c53.8-4.3,102.2-14.2,143.6-8.5c41.5,5.7,192,14.2,256.6-7.1C668.9,46.9,650.5,152.2,643.6,217.6z' //パスその2
];
var indexPath = 0;
var DURATION = 3000;

function playAnimation() {
//今がパス1ならパス0へアニメーション
if (indexPath === 0) {
$svg.animate(
{d: path[0] }, DURATION, playAnimation );
indexPath = 1;
//今がパス0ならパス1へアニメーション
} else {
$svg.animate(
{d: path[1] }, DURATION, playAnimation );
indexPath = 0;
}
}
playAnimation();

Snap.svg.jsのanimateメソッドにはコールバックが用意されているので、それを利用して再帰処理を加えています。

(また、最初のコードに比べるとコード量が若干長くなったので、変数など整理してます)


まとめ

右往左往しましたが、なんとかSVGでぐにょらせることができました。

SVGは動くと面白いんですが、けっこうハマりポイントが多いのでクロスブラウザ対応するのは厄介です。


  • filter, clip-pathはブラウザのサポート範囲が狭い。気をつけて。

  • Snap.svg.jsはjQUeryに似ているので使いやすい。

  • 今回のような一定の動きを維持させるアニメーションの場合は、パスの数を合わせる。

また、デバッグ中に以下のこともわかりました。

お気をつけ下さい。。。。


  • width,height,viewBox属性あたりを指定しないとIE10,11で謎の隙間が生まれることがある。(生まれないこともある)

  • Androidの古いやつ(4.1とか)だとそもそもSVGが表示されないこともある。(pngとかで代用できるならそっちのほうが楽)

本当はコレ以上にも書きたいことがたくさんあるのですが、まあまあの量になりそうなので、ひとまず今回はこれまでに。

機会があればLIGBLOGでまとめていこうかと思います。