2013年2月12日火曜日

パラパラアニメーションをsvgで作成する

パラパラアニメーションを扱うファイル形式の代表としてgifがあります.cssやflash等の競合技術が普及しつつ有る中でも,ちょっとずつずらした画像を連続表示するといった判りやすさから未だに広く扱われています.そこで今回はこのgifアニメーションをsvgで再現して見ることとします.

svgのアニメーション機構は先程のcssやflashに近いものですが,ちょっと工夫することでパラパラアニメーションに応用することもできます.なお実用に耐えうるかについては疑問ですが,出来る限りコードの内容を使いやすくかつ短くする方法についても考えてみます.

※2013/02/24追記)もうひとつの方法を追加.
※2014/10/25 この方法で作成したアニメーションは下記の点でWEBで使用するには不向きです.
・IEで動作しない
・最適化されていない(単なる画像の配列でしかない)
ですからまちがっても「アニメーションGIFに取って代わるもの」とはなり得ないのです.


パラパラアニメーションをsvgで作る意義


それではgif形式を用いずにsvgのアニメーション機構用いるメリットには何があるでしょう?例えば次のようなものが挙げられるでしょう.
  • 色数の制限を受けない.
    gif形式では256色が上限であるため,場合によって見た目が良くない場合がありました.svgでは事前に用意したjpgやpng形式の画像を使うことでこの制限がありません.
  • 編集が容易である.
    gif形式によるアニメーションを編集するには専用のツールが必要となるなど,必ずしも使い勝手の良いものではありません.svgでは基本的にテキストエディタで編集することができるため,後から修正するのが容易です.
  • アニメーションを重ね掛けすることができる.
    背景と前景とで異なるアニメーションをさせることができます.
このように様々な面で表現の幅や使い勝手が向上します.その一方でieで動作しない,全体としてのファイルサイズが肥大しがちであったりと,完全なgifアニメーションの代替となる訳でない点に注意してください.その為gifアニメーションの簡易動作確認をsvgで行うと言った用途に有効とも言えます.

準備


今回は既存のgifアニメーションをsvgアニメーションで再現することを目標とします.従って,事前に画像ツールでアニメーションgif画像を静止画(フレーム画)に分割しておきましょう.1から画像を作っても問題ありません.

筆者には絵心がありませんので,こちらのサイトで公開されている画像をお借りして作業を進めていくこととします.
箱庭的ドット絵
サイコウキオン-35℃



このアニメーションを分割すると次の4つの画像に分割されます.

従ってこの4つの画像を次々と入れ替えるようにすればパラパラアニメーションが完成するはずです.

手法1・image要素のxlink:href属性をアニメーション化する


この画像の入れ替えを実現するには様々な方法が考えられますが,image要素が参照する画像ファイルを切り替えてしまう方法が最も単純なものでしょう.動作確認

<?xml version="1.0" standalone="no"?>
<svg width="200px" height="298px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
 <image width="100%" height="100%">
  <animate attributeName="xlink:href" begin="0s" dur="0.5s" repeatCount="indefinite"
   values="
ame200_frame_0001.png;
ame200_frame_0002.png;
ame200_frame_0003.png;
ame200_frame_0004.png"
  />
 </image>
</svg>

animate要素は通常長さや色などの数値として表せる属性に作用して,滑らかなアニメーションを表現しますが,文字列を扱う属性に作用する場合は,動作モードがdiscrete(離散的)として扱われます.従ってa要素のxlink:href属性は外部画像へのuri文字列を扱うため,このように記述するだけでimage要素の画像参照先が次々と変化し,パラパラアニメーションとして機能します.

この方法の問題点


仕組み上はこれで正しそうなのですが,実際に試してみると次の問題点に突き当たります.
  • アニメーション開始時に画像の読み込みがなされていない.
    animate要素によってxlink:href属性の中身が切り替わったタイミングで初めて画像が読み込まれるため,画像の表示が間に合いません.その為,アニメーションが始まって暫くの間はフレームが欠けたように動作します.
    この問題はsvg1.2tinyにおいてprefetch要素として解決するはずでしたが,svg1.2tinyは動作環境が少なく,この方法は使えません.
  • そもそもchromeで動作しない.
    chromeはsmilアニメーションの実装が甘いため,image要素のxlink:href属性へのアニメーションが動作しません.
begin属性に0sを指定したアニメーションは通常svg要素のSVGLoadイベントと共に起動するため,前者の問題は予めimage要素を読み込むようなコードを仕込むことで解決するのですが,chromeで動作しないのはどうしようもありません.従って別の方法を探る必要があります.

手法2・画像を並べる


もっと原始的にアニメーションを考えてみましょう.すると映画フィルムのように予め画像を並べておき,1コマずつずらして表示すれば良いことになります.そこでこの方針に従ってコードを記述すると次のようになります.動作確認

<?xml version="1.0" standalone="no"?>
<svg width="200" height="298" viewBox="0 0 200 298"
 xmlns="http://www.w3.org/2000/svg" 
 xmlns:xlink="http://www.w3.org/1999/xlink" 
 version="1.1">
 <svg overflow="visible">
  <image xlink:href="ame200_frame_0001.png" x="0" width="200" height="298"/>
  <image xlink:href="ame200_frame_0002.png" x="200" width="200" height="298"/>
  <image xlink:href="ame200_frame_0003.png" x="400" width="200" height="298"/>
  <image xlink:href="ame200_frame_0004.png" x="600" width="200" height="298"/>
  <animate attributeName="x" 
   begin="0s" dur="0.5s" repeatCount="indefinite" 
   calcMode="discrete"
   values="
   0;-200;-400;-600
  " />
 </svg>
</svg>

画像全体を一括してずらしたいため,svg要素を2重にして定義しています.また内側のsvg要素内部の画像が確実に表示されるようにoverflow属性にvisibleを指定しています.
先程懸念された画像の事前ロード問題も,アニメーション開始前に全ての画像がimage要素で読み込まれていることから解決しています.

しかしこのコードでは元となる画像の大きさに依存した記述がそこかしこに存在しています.その為,svgファイルを他のアニメーションに流用したり,画像の大きさを変更する際に非常に面倒なこととなります.

画像サイズの呪縛を解く


svgにはそもそも優秀な画像リサイズ機構が含まれていますから,これをなんとか利用できないか?そう考えて改良したものが次のコードです.動作確認

<?xml version="1.0" standalone="no"?>
<svg width="200" height="298" viewBox="0 0 1 1"
 xmlns="http://www.w3.org/2000/svg" 
 xmlns:xlink="http://www.w3.org/1999/xlink" 
 version="1.1" preserveAspectRatio="none">
 <svg overflow="visible">
  <image xlink:href="ame200_frame_0001.png" x="0" width="1" height="1" preserveAspectRatio="none"/>
  <image xlink:href="ame200_frame_0002.png" x="1" width="1" height="1" preserveAspectRatio="none"/>
  <image xlink:href="ame200_frame_0003.png" x="2" width="1" height="1" preserveAspectRatio="none"/>
  <image xlink:href="ame200_frame_0004.png" x="3" width="1" height="1" preserveAspectRatio="none"/>
  <animate attributeName="x" 
   begin="0s" dur="0.5s" repeatCount="indefinite" 
   calcMode="discrete"
   values="
   0;-1;-2;-3
  " />
 </svg>
</svg>

svgコード内部での画像サイズを1×1に再定義することで画像そのもののサイズ設定をルートとなるsvg要素の一箇所に集約することが出来ました.なお,元となる画像のアスペクト比を調整するために新たにpreserveAspectRatio属性が追加されています.

これで比較的扱いやすくはなりましたが,属性の記述が増えてしまったためいささかコードが冗長となってしまった印象があります.

dtdによる属性値の共通化


この問題はsvgがそもそもxmlであることを思い出すことで解決します.dtdを定義することで要素の初期値を指定したり,文字列の共通部分を1箇所にまとめることが出来るのです.動作確認

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg[
 <!ATTLIST image width CDATA "1" 
     height CDATA "1" 
     preserveAspectRatio CDATA "none">
 <!ENTITY fname "ame200_frame_00">
]> 
<svg width="200" height="298" viewBox="0 0 1 1"
 xmlns="http://www.w3.org/2000/svg" 
 xmlns:xlink="http://www.w3.org/1999/xlink" 
 version="1.1" preserveAspectRatio="none">
 <svg overflow="visible">
  <image xlink:href="&fname;01.png" x="0"/>
  <image xlink:href="&fname;02.png" x="1"/>
  <image xlink:href="&fname;03.png" x="2"/>
  <image xlink:href="&fname;04.png" x="3"/>
  <animate attributeName="x" 
   begin="0s" dur="0.5s" repeatCount="indefinite" 
   calcMode="discrete"
   values="
   0;-1;-2;-3
  " />
 </svg>
</svg>

ここではimage要素の初期値を設定しています.image要素はheight,width,preserveAspectRatio属性が全く同じ値を取るため,ATTLIST宣言により1箇所にまとめることができます.また,ファイル名についてもENTITY宣言を用いて共通化しています.このようにかなりの部分を整理することが出来ました.

なおdtdによる値を共通化した場合,inkscape等のドローイングツールでは内容の編集ができなくなります.従ってこの方法はsvgを手書きする場合に限って有効なテクニックと言えます.

もうひとつの方法:use要素を使う


その後いろいろ試してみた結果,use要素を使う方法も有効なようです.webkit環境ではimage要素のxlink:href属性をアニメーション化することが困難でしたが,use要素のxlink:href属性はアニメーション化できます.従って次のように一旦image要素で各フレームを読み込んでおき,use要素の参照先を変更することでもパラパラアニメーションを実現することができます.動作確認

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg[
 <!ATTLIST image width CDATA "100%" 
     height CDATA "100%" 
     preserveAspectRatio CDATA "none">
 <!ENTITY fname "ame200_frame_00">
]> 
<svg width="200px" height="298px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="main">
 <defs>
  <image xlink:href="&fname;01.png" id="f1"/>
  <image xlink:href="&fname;02.png" id="f2"/>
  <image xlink:href="&fname;03.png" id="f3"/>
  <image xlink:href="&fname;04.png" id="f4"/>
 </defs>
 <use>
  <animate attributeName="xlink:href" begin="0s" dur="0.5s" repeatCount="indefinite"
   values="#f1;#f2;#f3;#f4"
  />
 </use>
</svg>

先ほどの描画位置をずらす方法に比べ,コードが非常に明快であり,普段使いの場合はこの方法をとるのがベストと思われます.

インラインsvgに応用する場合の注意点


なおインラインsvgでこの方法を採用する場合はsvg要素にxlinkの名前空間を定義しておく必要があります.
例)<svg xmlns:xlink="http://www.w3.org/1999/xlink">…
html5では通常勝手にsvg要素を判断することとなっているのですが,webkitはanimate要素のattributeName属性の中身まではチェックしておらず,この記述を忘れるとアニメーションが正しく動作しません.(おそらくバグ)

最後に


gifアニメーションをsvgで再定義することにどれほど意味があるかは判りませんが,svgアニメーションを理解する題材としては非常に面白いものがあります.この楽しさは実際にsvgを手書きしないと判らないため,是非試してみてください.

0 件のコメント:

コメントを投稿