SVGのclipPath要素を使ってモーフィングアニメーションする
もしかしたら中には難しいことをせずに、画像でマスクしてトリミングしている方もいるかもしれませんが、今回は以下の理由があり、SVGのclipPath
要素を使うことにしました。
- デザイナーさんから手書き風に画像をトリミングしたいという要望があった
- クライアントがCMSから画像を更新するとき、画像を加工する手間を省きたい
- パスをモーフィングアニメーションさせたい
実際のコードは以下のURLからご覧ください。
- Part1 Responsive ClipPath Morphing Animation with snap.svg
- Part2 Responsive ClipPath Morphing Animation with snap.svg
※Part1のソースコードはなんだかぎこちなかったので、改良したソースコードがPart2になります。
実装方法
まずはざっくり大まかな手順をご紹介すると、以下の通りに実装します。
- Illustratorのファイルでトリミングしたい形のパスを2種類作る
- そのパスの1つをコピペして
clipPath
内のpath
要素のd属性に挿入する - 2つめのパスは上記と同じ
path
要素にdata-to
属性に挿入する - JSで動かす
Illustratorのファイルでトリミングしたい形のパスを2種類作る
このとき、パスはアニメーション元を複製してアニメーション後のパスを作成したほうが吉です。そして、アンカーの数はアニメーション後も同じ数であることを気をつけて作成してください。
1つめのパスを保存する
Illustrator CCからはパスをコピーすると、テキストエディタにペーストできるようになっています。とても便利です。個人的にはSketchが吐き出すSVGのコードよりもIllustratorのSVGコードのほうが好きなので、Sketchにパスがあった場合にはSVGでエクスポート後、Illustratorで開いて再度SVGとして保存するようにしています。(ただ、ベストはデザイナーさんがパスを用意してくれることだと思います)
2つめのパスを保存する
もし2つめのパスの幅や高さが最初のパスと違った場合、SVGは左上から始まるため、パスの開始位置が変わってしまう場合があります。2つ目のパスを保存するときは、最初のパスの幅と高さに合ったキャンバスを新規作成し、そこに2つめのパスをペーストし、「別名で保存する」するようにしています。
パスの1つをコピペしてまずはクリップパスを実装する
<svg version="1.1" x="0px" y="0px" width="700px" height="290px" viewBox="0 0 700 290"> <image xmlns:xlink="http://www.w3.org/1001/xlink" xlink:href="http://placekitten.com/700/290" width="700px" height="290px" clip-path="url(#circle)"></image> <clipPath id="circle"> <path class="wave-path" d="M93.4,281.8C237.9,298.2,564.2, ...(省略) "></path> </clipPath> </svg>
svg
要素内に、image
要素(HTML5では画像を表示する要素はimg
要素だけどSVGだとimage
要素)を記入し、同じsvg
要素内にclipPath
要素を記述する。
clipPath
要素内には先ほどIllustratorで作成したアニメーション前のパスをpath
要素として挿入します。このとき、clipPath
要素とimage
要素をid
属性で紐付けます。
2つめのパスはpath
要素にdata-to
属性に挿入する
<svg version="1.1" x="0px" y="0px" width="700px" height="290px" viewBox="0 0 700 290"> <image xmlns:xlink="http://www.w3.org/1001/xlink" xlink:href="http://placekitten.com/700/290" width="700px" height="290px" clip-path="url(#circle)"></image> <clipPath id="circle"> <path class="wave-path" d="M93.4,281.8C237.9,298.2,564.2,...(省略)" data-to="M112.5,261.6c132.5,24.1,415,19.8,463.2,7.4 c46.7-12,85.2-41.3,90.7-63.3c5.5-22,...(省略)"></path> </clipPath> </svg>
アニメーション前のパスを指定したd
属性と同じpath
要素内に、アニメーション後のパスを指定するdata-to
属性を新しく追加します。なお、この時点ではdata-to
属性のパスは表示に影響しません。
Snap.svgでパスを動かす
JSで動かすときは、私の場合path要素にアニメーションを開始したいパスをd
属性で指定し、アニメーション後のパスはdata-to
属性というオリジナルのdata属性を作成し、挿入します。
今回はSnap.svgというJSのライブラリを使い、d
属性とdata-to
属性で指定したパスの値をループしてアニメーションできるようにしました。
var snaps = Snap.selectAll('.wave-path'); // それぞれの.wave-pathに対してアニメーションを実行する [].forEach.call(snaps,function(snap){ var pathFrom = snap.attr('d'); var pathTo = snap.attr('data-to'); function infAnim( el ,dir) { var start,end; if(dir ==='up'){ start = pathFrom; end = pathTo; dir = 'down'; }else{ start = pathTo; end = pathFrom; dir = 'up'; } el.attr('d', start); el.animate({d:end},3000,mina.linear,function(){ infAnim(el,dir); }); } infAnim(snap,'up'); })
実装途中につまずいた点
SVGは使うたびにどこかでつまずいてしまうので、今回使った点で苦労した点を以下にまとめます。
- はじめCSSのclip-pathプロパティで実装しようとしてつまずいた
- 画像が
clipPath
で指定したパスにそわずに途切れてしまう - 用意されたパスでうまくアニメーションしなかった
はじめCSSのclip-pathプロパティで実装しようとしてつまずいた
クリップパスを実装する方法としては、私が今回採用したSVGのclipPath
要素を使う方法と、CSSからトリミングしたいクリップパスのid
属性を指定する方法があります。はじめ、CSSでトリミングする方法を採用しようとしたのですが、なかなか扱いが難しく、ブラウザ対応もまだまだということもあり、断念しました。株式会社まぼろしの松田さんにSVGのclipPath
要素を使う方法を教えていただき、実装することができました。個人的にはclipPath
要素で指定する方がトラブルが少なく実装できました。
画像がclipPath
で指定したパスにそわずに途切れてしまう
試行錯誤していたら、画像のザイズやSVGの大きさが切り抜きたいサイズに合っていなかったことが原因でした。width
を100%にしたらなんとかいい感じに収縮してくれるだろうという甘い考えがあったのですが、そんなことはありませんでした。サイズはきっちり指定しましょう。
用意されたパスでうまくアニメーションしなかった
デザイナーさんにIllustratorでアニメーションするためのパスを2種類用意していただいたのですが、なぜかパスが指定した形ではなく、SVGの中心へと縮む現象が起きてしまいました。コードを見てみると、新しくパスを作られたようで、アニメーション元のパスを複製してアニメーション後のパスを作ると正常にアニメーションされました。
アニメーション元のパスからアニメーション後のパスを作ったほうが無難なようです。
最後に
実はSnap.svgではなくGreenSockを使ってみよう!と思ったのですが、JSのスキルが無い私にはサンプルコードがまだ少ないGreenSockはハードルが高かったので、断念しました。力不足でとても悔しいので、もうすこし私のJSスキルの上昇、またはGreenSock関連の記事が増えてきたら再度トライしたい...!
以下、参考になった記事のご紹介です。
- Snap.svgで快適SVGアニメーション - Snap.svgとは | CodeGrid
- SVG奮闘記 ー SVG要素をぐにょぐにょ動かす実装を完了させるまでに右往左往した話 - Qiita
- using snap.svg t - JSFiddle
あとは、ちょうどセミナーに講師としてお呼びしていたまぼろしの松田さんに直接クリップパスについてお伺いしたり、JavaScriptの面ではアップルップル堀さんにもご協力いただきました。私の知識だけでは実装できなかったので、とても助かりました。ありがとうございました!