2014年10月29日水曜日

img要素内のSVGパラパラアニメーションをIEでも動かす


最近話題になった「ついにGIFに代わる新しいアニメーション画像フォーマットが誕生か」として公開されたxng形式でしたが,実際には単なるSVGの仕組みを応用したパラパラアニメーション(パラパラマンガ)でした.

SVGではCSSやスクリプト無しにアニメーションが動作するため,驚かれた方も多かったと思われますが,このやり方でのパラパラアニメーションの実現には次のような問題が存在します.
  • 最適化されていないことでファイルサイズが増大する
    データURIスキーム形式で静止画をSVGファイルに埋め込む際に,画像データが増大します.また,動画形式と異なり,フレーム間での最適化が効きません.
  • そもそもIEで動作しない
    IEはSVGアニメーション(SMILアニメーションと呼びます)をサポートしていません.
これらの点で運用には細心の注意が必要です.

では上記の問題を解決するにはどうすればよいのでしょう?
前者は各静止画の品質を落としたり,フレーム数を減らすなどで対処できるかもしれません.
では後者のIEで動作しない問題は?
そこで本記事ではSVGの構造を見直し,javascriptと組み合わせることで無理矢理img要素でのアニメーションを動かしてみました.

なお,SVGを使ったパラパラアニメーションの原理については次の記事を見てください.

xng形式での内部構成とは大分異なりますが,画像データを埋め込んでいないこと以外やっていることは概ね一緒です.



一聞は百見に如かず


とにかく見てみましょう



どうです?IEでも動いていますよね.コード上も単なるimg要素にSVGを読み込ませているだけです.
下はその内容です.
  • SVGコード
    <?xml version="1.0"?>
    <svg xmlns="http://www.w3.org/2000/svg" 
     xmlns:xlink="http://www.w3.org/1999/xlink" 
     width="200" height="298" viewBox="0 0 1 1" preserveAspectRatio="none">
     <!--for stand alone viewing-->
     <script xlink:href="flipBookSVG.js"/>
     <!--for supporting SMIL animation-->
     <animate attributeName="viewBox" values="0 0 1 1;1 0 1 1;2 0 1 1;3 0 1 1" 
      calcMode="discrete" begin="0s" dur="0.8s" repeatCount="indefinite"/>
     <!--for IE to slide view of img element by changing hash value-->
     <view viewBox="0 0 1 1" id="0"/>
     <view viewBox="1 0 1 1" id="1"/>
     <view viewBox="2 0 1 1" id="2"/>
     <view viewBox="3 0 1 1" id="3"/>
     <!--line up images-->
     <image width="1" height="1" preserveAspectRatio="none" xlink:href="data:image/png;base64,..."/>
     <image x="1" width="1" height="1" preserveAspectRatio="none" xlink:href="data:image/png;base64,..."/>
     <image x="2" width="1" height="1" preserveAspectRatio="none" xlink:href="data:image/png;base64,..."/>
     <image x="3" width="1" height="1" preserveAspectRatio="none" xlink:href="data:image/png;base64,..."/>
    </svg>
  • HTMLコード
    <!DOCTYPE html>
    <html>
     <head>
      <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
      <title>img要素内のSVGパラパラアニメーションをIEでも動作させてみた</title>
      <script src="flipBookSVG.js"></script>
     </head>
     <body>
      <a href="source.svg" target="_blank">
       <img src="source.svg" class="flipbook"/>
      </a>
     </body>
    </html>
  • flipBookSVG.js
    "use strict";
    (function(){
     if(!window.SVGElement || window.SVGAnimateElement){return;}
     document.addEventListener("DOMContentLoaded", 
      document instanceof HTMLDocument 
       ? animateFlipBookInHTML: animateFlipBookSVG, false);
     //in HTML animate img elements.
     function animateFlipBookInHTML(){
      Array.prototype.forEach.call(
       document.querySelectorAll("img.flipbook"), 
       animateFlipBook);
      function animateFlipBook(img){
       var src = img.src;
       var req = new XMLHttpRequest();
       req.open("GET", src);
       req.onload = function(){
        var doc = this.responseXML;
        if(!doc){return;}
        var p = getParams(doc);
        if(p.fpms<=0){return;}
        var start;
        animate();
        function animate(){
         try{
          if(!img.complete){return;}
          if(!start){start = Date.now();}
          var time = Date.now() - start;
          img.src = src + "#" + (Math.floor(time/p.fpms)%p.cnt);
         }finally{
          requestAnimationFrame(animate);
         }
        }
       };
       req.send();
      }
     }
     //in SVG animate itself.
     function animateFlipBookSVG(){
      var p = getParams(document);
      if(p.fpms<=0){return;}
      var start;
      var vb = document.documentElement.viewBox.baseVal;
      animate();
      function animate(){
       if(!start){start = Date.now();}
       var time = Date.now() - start;
       vb.x = Math.floor(time/p.fpms)%p.cnt;
       requestAnimationFrame(animate);
      }
     }
     function getParams(doc){
      var cnt = doc.querySelectorAll("view").length;
      var fpms = doc.querySelector("animate")
       .getAttribute("dur").replace("s", "") / cnt * 1000;
      return {cnt: cnt, fpms: fpms};
     }
    })();

結局何をやっている?


なんだか面倒なことをしているように見えるかも知れませんが,要は予め静止画像を横に並べておき,svg要素のviewBox属性を上書きすることで表示される内容を書き換えているのです.
その際の手段としてIEではview要素を,それ以外ではanimate要素を使っています.


view要素とは


SVGではviewBox属性を使ってグラフィックの描画範囲を変更することができますが,img要素のsrc属性にハッシュ値としてview要素のIDを指定すると,view要素での値でviewBoxの内容を上書きすることができるのです.

従って,今回表示している「source.svg」 にハッシュ値「#0」「#1」「#2」「#3」を追加すると次のようになります.



フレームごとに画像が切り替わっていることが判ります.
※IE以外の環境ではanimate要素の内容が優先されるため,全く同じアニメーションが表示されます.

ここまでくれば後は単純で,スクリプトを使ってimg要素のsrc属性を次々に書き換えていくだけです.

IEでも動くパラパラアニメーション・作成手順

  1. 同じサイズの画像を準備し,そのサイズを記述したSVGファイルを用意します.
  2. SVGファイルのimage要素にデータURIスキーム形式で画像データを埋め込み,1列に並べます.
  3. アニメーションのための仕掛けを記述する.
    • IE以外:viewBox属性を書き換えるanimate要素を定義します.
    • IE:1画像毎のview要素を定義します.
  4. HTMLファイルに上記SVGファイルを読み込むimg要素を定義する.その際,classとして「flipbook」を指定します.
  5. HTMLファイルにflipBookSVG.jsを追加します.

課題

と,確かにIEでもパラパラアニメーションが動作するようになりましたが,その仕組み上動作が若干ぎこちなく,安定しているとは言えません.やはり,アニメーション画像を作るのであればこれまでどおりGIF形式や動画形式を利用する方が無難と言えるでしょう.

0 件のコメント:

コメントを投稿