SVGを変形させたりアニメーションさせることができる、Adobeがオープンソースで提供しているJavaScriptライブラリである「Snap.svg」を使って、SVGのpathを別のpathにモーフィングさせるアニメーションサンプルをご紹介します。
SVGを操作するライブラリはSnap.svgよりもパフォーマンスが良いとされるsvg.jsなどがありますが、パスとアニメーションをより簡単に扱えるSnap.svgを利用してみました。
今回のサンプルは、WordPressのギャラリーやポートフォリオタイプのアーカイブページの記事一覧で、各記事をマウスオーバーした際に、レイヤー用に表示しているSVGを「にゅる」っと変形させるようなモーションエフェクトをイメージしています。
INDEX
Snap.svgを使ったSVGのpath変形アニメーションデモ
See the Pen Hover Effect with SVG using Snap.svg.js by digistate (@digistate) on CodePen.
HTML/SVG/CSSの構成
HTMLの基本構成
HTMLは、アーカイブページをイメージして、各記事をarticle
タグで括っています。
その中にあるSVGは、figure
タグで括っておきます。
<section id="grid"> <article style="background-image:url('背景用画像のURL')"> <a href="#"> <figure> <svg viewBox="0 0 200 200" preserveAspectRatio="none"></svg> <figcaption> <h2>記事のタイトル</h2> <p>ディスクリプションなど。</p> </figcaption> <button>ダミーボタン</button> </figure> </a> </article> : : </section>
各記事(article)の背景画像をセット
ギャラリー形式のように各記事は画像を並べたデザインにするため、article
タグにインラインCSSで背景画像(アイキャッチ)をbackground-image
でセットしておきます。
<article style="background-image:url('背景用画像のURL')">
記事(article)のCSS
ギャラリーをイメージして、各記事(article)を表示したアーカイブページデザインは画像を並べた状態にするため、article
は左右の回り込みが可能なインラインのブロック要素(inline-block)にします。
3カラムにするために、幅はビューポート(ブラウザの表示サイズ)に対しておよそ33%の幅、高さは適当に500ピクセルにしています。
article { position:relative; display: inline-block; min-width:280px; width: 33.333vw; /* ビューポートに対して33.333% : 3カラム表示 */ height: 500px; /* お好みで */ background-position:center; background-size:cover; }
svgタグの構成
svgタグには、viewBox
という属性でビューポートに対する位置とサイズを指定しておく必要があります。
ビューポートというのは、svgの描画エリアのサイズ(width, height)で、これに対してviewBoxは、描画エリアの左上の頂点を原点とした描画開始位置の座標(x, y)と、描画サイズ(width, height)を viewBox="x y width height"
として定義します。
ビューポートとviewBoxの関係は混乱しがちなので、通常は以下のように双方のサイズは同じにしておくと扱いやすくなります。
<svg width="200" height="200" viewBox="0 0 200 200">〜</svg>
SVGのviewBoxについての詳細は、以下が参考になります。
今回のデモでは、svgタグはCSSでwidth, heightを100%にしているため、ビューポート(width, height)は未指定ですが、viewBoxはとりあえず [0, 0, 200, 200] という正方形のサイズをセットし、後にJavascriptで描画するsvgの図形(実際はパス)の幅もこのviewBoxで指定した200ピクセルで作成します。
さらに、描画するsvgのアスペクト比の扱いを指定するpreserveAspectRatio
属性は、svgをレスポンシブ化させて描画エリアに常に全体を表示させるため、「none」(指定しない)にしておきます。
ここまでのviewBoxとpreserveAspectRatioの設定と、後述する描画図形の幅を200ピクセルで作成することによって、レスポンシブデザインに対応した、ホバー用のSVGレイヤーが出来上がります。
<svg viewBox="0 0 200 200" preserveAspectRatio="none"></svg>
SVG関連のCSS
figure要素のCSS
svgを包括するfigure
要素は、親要素のarticle
のサイズと同じにしてフィットさせるため、以下のスタイルを定義しています。
figure { position: relative; overflow: hidden; width:100%; height:100%; }
svg要素のCSS
先述のsvgはarticle
要素いっぱいに描画サイズをセットします。top:-1px
は、Mac版のFireFox 26.0にて発生する意図しない不明な線がでてしまうレンダリング不具合を補うために0px
ではなく-1px
上方に移動しています。
svg { position: absolute; top: -1px; /* FireFox 26.0(Mac) のレンダリング不具合補完のため */ left:0; z-index: 10; width: 100%; height: 100%; }
figcaption要素のCSS
figure要素のコンテンツのキャプションを意味するfigcaption
要素もfigureに対してぴったり重ねるため、以下のようにします。
figcaption { position: absolute; top: 0; z-index: 11; padding: 10px; width: 100%; height: 100%; text-align: center; }
その他、投稿タイトル(h2)やキャプション、ダミーの「Read More」ボタンの装飾についてはここでは割愛しますが、お好みで。
描画するsvgのパスの作成と塗りつぶしカラーの指定
今回のデモでは、svgパスのモーフィングアニメーションを行うことと、簡単にレイヤー用のパスとマウスオーバー時のパスを別のものに置き換えることができるよう汎用性を考え、svg要素の中に直接path要素を組み込んで描画するのではなく、記事(article)をすべて包括するsection
要素に、data-path-from
(初期状態のパス)、data-path-to
(マウスオーバー時にモーフィングした後のパス)、data-fill-color
(パスを塗りつぶすカラー)というdata属性に任意の値をセットしておき、Javascript側で指定したパスと塗りつぶしカラーを取得してSnap.svgで描画する、という仕組みにしています。
section要素の構成
<section id="grid" data-path-from="初期状態のレイヤー用パス情報" data-path-to="マウスオーバー時のパス情報" data-fill-color="パスの塗りつぶしカラーコード">
描画したいsvgのパス情報をつくる
描画したいsvgの作成と、そのパス情報を取得するは、Illustratorのような高価なソフトウェアを利用せずとも、オープンソースのベクトル画像編集ソフトの「Inkscape」を使えば簡単に作成・取得できます。
表示したいsvgの図形を初期状態のものとマウスオーバー時の状態の2つを順番に作成します。
このとき、図形の幅はsvg要素のviewBox属性に指定した幅と同じにして作成します。
作成したら、図形を選択した状態で、メニューから「パス」→「オブジェクトをパスへ」でパスに変換します。
さらに「編集」→「XMLエディタ」を開きます。
svg:path
のd属性の値が求めるパス情報なので、これをコピーします。
section要素にレイヤー用のpath情報と塗りつぶしカラーをdata属性にセット
コピーした初期状態とマウスオーバー後の2つのパス情報を、それぞれdata-path-from
とdata-path-to
属性にセットし、パスの塗りつぶしカラーのコードはdata-fill-color
属性にセットします。
<section id="grid" data-path-from="m 0,-0.5337091 200,0 0,86.9148401 c -102.826143,-34.275781 -94.88403,33.170919 -200,0 z" data-path-to="m 0,-0.5337091 200,0 0,45.5189141 c -98.72778,19.754056 -96.52337,-23.611381 -200,0 z" data-fill-color="rgba(255,255,255,0.8)">
Javascriptでsvg要素に図形を描画
いよいよSnap.svgの力を借りて、svg要素にpath要素を組み込んでsvgレイヤーを描画します。
パラメータのセット
まずはパス情報や塗りつぶしカラーの取得や、各種パラメータをセットします。
var duration = 400, // アニメーション時間 easing = mina.backout, // イージングの種類(Snap.svgで提供されているもの) grid = document.querySelector("#grid"), // data属性を持つsection要素の取得 items = document.querySelectorAll("#grid article"), // 記事(article)要素をまとめて取得 is_hover = false; // マウスオーバー状態判定用
Snap.svgでサポートされるイージングの種類は以下があります。
イージングの種類
- mina.linear
- mina.easeout
- mina.easein
- mina.easeinout
- mina.backin
- mina.backout
- mina.elastic
- mina.bounce
data属性の値(svgのパス情報と塗りつぶしカラー)を取得
section要素を代入している grid オブジェクトに getAttribute
で data-path-from, data-path-to, data-fill-color属性の値を取得します。
var path_config = { from : grid.getAttribute('data-path-from'), // 初期状態のパス to : grid.getAttribute('data-path-to'), // マウスオーバー時のパス fill_color : grid.getAttribute('data-fill-color') // パスの塗りつぶしカラー };
各記事に対して順番にpathを描画する
[].slice.call(arguments)
という書式を用いて querySelectorAll
で取得した 配列ライクなオブジェクト(items)のアイテムを順番に処理していきます。
[].slice.call(items).forEach(function(el) { // ここで順番に処理 }
ここでいよいよ Snap.svgの出番です。
article要素内のsvg要素に対して初期状態のパスを描画するためのオブジェクトを生成します。
var snap = Snap(el.querySelector('svg')), // svg要素を指定 path_svg = snap.path(path_config.from).attr({ // snapオブジェクトにパスを指定 fill: path_config.fill_color // 塗りつぶしカラー } );
あとはマウスオーバーをトリガーにしてSnapオブジェクトにアニメーションさせてパスを表示させます。
// マウスオーバー時 el.addEventListener('mouseenter', function() { if (!is_hover) { // パスをマウスオーバー時のパスにアニメーションさせる path_svg.animate( {'path' : path_config.to}, // パスの指定 duration, // アニメーション時間 easing); // イージングの種類 is_hover = true; // ホバー状態判定フラグ } }); // マウスオーバーが解除されたとき el.addEventListener( 'mouseleave', function() { if (is_hover) { // パスを初期状態のパスにアニメーションさせる path_svg.animate( {'path' : path_config.from}, // 初期状態のパスを指定 duration, easing ); is_hover = false; } });
ここまでの流れをまとめると、各記事(article)へのループ処理全体は以下のコードになります。
[].slice.call(items).forEach(function(el) { var snap = Snap(el.querySelector('svg')), path_svg = snap.path(path_config.from).attr({ fill: path_config.fill_color }); el.addEventListener('mouseenter', function() { if (!is_hover) { path_svg.animate( {'path' : path_config.to}, duration, easing); is_hover = true; } }); el.addEventListener( 'mouseleave', function() { if (is_hover) { path_svg.animate( {'path' : path_config.from}, duration, easing ); is_hover = false; } }); });
今回のデモでは、すべて同じパスの前提なのでarticle
の親要素のsection
にパス情報をdata属性にセットしていますが、記事ごとに異なるパスや塗りつぶしカラーをセットしたい場合は、article
要素ごとに異なるdata属性をセットして、Javascriptでも各article要素のdata属性の値を取得するようにすれば可能になります。
参考
codrops/Shape Hover Effect with SVG
Snap.svgを使って、一手間かけたマウスオーバーアニメーションを作成したい!