2012年9月25日火曜日

svgアイコンセットをuse要素から使いやすくする

http://www.ar-ch.org/mt/archives/2012/09/svg60.html
こちらで配布しているsvgアイコンファイルを元にuse要素から使いやすくするスクリプトを書いてみた.なお,実際の動作サンプルを掲載すると利用条件の「再配布の禁止」に抵触する可能性があるため,あくまで技術検証としてスクリプトのみ掲載する.なお,他のアイコンセットについても似たような構造であれば応用できると思う.

通常svgで配布されるアイコンセットはillustrator等のドローイングツールで作成されるため,そのままhtmlから利用するとなると使いにくい場合が多い.というのも,次のような問題があるからだ.
  • 図形の位置と大きさがバラバラである.画像の一部を取り出すのが面倒.
  • 色が固定されている.ツールで出力した際の色をそのまま引き継ぐため.
従って実際に利用するには再度ドローイングツールなどを使って画像を切り貼りして使う必要がある.(これが本来の方法ではある.)

だが,いちいちこのような作業を行うのは面倒なので,何とかして楽をしたいところ.そこで,svgはhtmlやjavascriptと親和性が高い事を利用してsvgの内容を書き換えてしまおうというのが今回のお話.こうすることでツールで作ったアイコンを少ない手順でhtmlに挿入することが可能となる.

目標としては次の通り.アイコンをuse要素から使うことを想定している.
  • 図形のサイズ・位置を共通化する.
    指定した矩形範囲に全ての図形が配置されるようにする.
  • 図形にidを付与する.
    アイコンを参照する際,use要素から単体のアイコンを抽出可能とする.
  • fill属性を削除・変更する.
    削除しておけば,使う側でスタイルを変更することができる.
これを実現したものが下の方に記載したソースコードだ.
スクリプト自体はchrome及びoperaでも動作する(つもりだ)が,出来ればfirefoxで確認して欲しい.
※下がそのスクリーンショット.多少ゴミが残っているけれど,これは元ファイルの構成によるものなので,気にする必要なし.


使い方
  • 下記のコードをhtmlとしてicon_basic.svgと同じディレクトリに保存し,実行すると.画面上のリンクが有効となるので,ここから変換結果のsvgを取り出し,任意のディレクトにに保存する.
  • このsvgをuse要素から参照する.「result.svg」としたのであれば次のようなコードをhtmlに挿入する.
    <svg viewBox="0 0 30 30"><use xlink:href="result.svg#icon_3"/></svg>
    ここで「#icon_3」はスクリプトが勝手につけたidなので,適宜内容を書き換えても良い.
  • cssからuse要素にスタイルを設定する.
    use{fill:red} …アイコンに直接色を指定したい場合
    use{fill:currentColor} …svg要素を含む要素(div等)のcolorプロパティの値を引き継ぎたい場合
※詳しくはhttp://defghi1977-onblog.blogspot.jp/2012/07/svg_30.htmlを参照のこと.
※フォントでは無いのでtext-shadow等の効果は無効だが,filterにより同様のことは可能だ.

スクリプトの動作原理は次の通り.
  1. object要素に元となるsvgを読み込む.
  2. object要素内部のdomにアクセスし,fill属性を削除する.
  3. アイコン図形の要素を抽出し,getBBoxメソッドを使って描画範囲を抽出する.
  4. 描画範囲を元にtransform値を計算し,アイコン図形を原点付近に平行移動する.
    (アイコンサイズを念頭に入れる.)
  5. svgの内容をコピーし,htmlのdiv要素に挿入する.これはinnerHTMLプロパティからsvgのソースコードを抽出するため.
  6. (アイコンの使い方サンプルを出力する. 処理自体には関係ない)
  7. svgのソースをdataスキーム形式に変換してa要素のhref属性に設定し,ダウンロード可能とする.
※アイコン図形の定義内容によってはstyle属性を削除したり,domの検索文字列を変更すると良い.
    <!DOCTYPE html>
    <html>
     <head>
     <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
     <style type="text/css">
    object[data]{
     width:0;height:0;
    }
    div#tmp{
     width:0;height:0;overflow:hidden;
    }
    #using>svg{
     width:40px;
     height:40px;
    }
    #using{
     color:orange;
    }
    #using use{
     fill:currentColor;
    }
     </style>
    
     </head>
     <body>
      <object data="icon_basic.svg"></object>
      <div id="tmp"></div>
      <a>変換結果svg</a>
      <div id="using">svgアイコンの使用例(firefoxで動作)<br/></div>
      <script>
    document.querySelector("object").onload = function(){
     
     //アイコンの大きさ
     var ICON_SIZE = 40;
    
     //svgを読み込むobject要素
        var objElem = document.querySelector("object");
     var svg = objElem.contentDocument.documentElement;
     
     var i,len;
     
     //fill属性を削除
     var elems = svg.querySelectorAll("*[fill]");
     for(i=0, len=elems.length;i<len;i++){
      elems[i].removeAttribute("fill");
     }
     
     //図形要素の位置を原点付近に移動
     var children = svg.childNodes;
     var child;
     var icon;
     var count = 0;
     for(i=0, len=children.length;i<len;i++){
      child = children[i];
      //テキストノードは省く
      if(child.nodeType != 1){
       continue;
      }
      icon = child;
    
      //描画領域を抽出して原点付近に移動
      var bbox = icon.getBBox();
      var translate = svg.createSVGTransform();
      translate.matrix.e = ICON_SIZE/2 - bbox.x - bbox.width/2;
      translate.matrix.f = ICON_SIZE/2 - bbox.y - bbox.height/2;
    
      icon.transform.baseVal.appendItem(translate);
      count++;
      icon.id = "icon_"+count;
     }
     
     //div要素に挿入.(svgのソースコードを取得するため)
     var div = document.querySelector("div");
        div.appendChild(svg.cloneNode(true));
     
     //use要素を使うサンプル
     var using = document.querySelector("#using");
     for(i=0;i<count;i++){
      //サンプル表示
      using.innerHTML += '<svg viewBox="0 0 40 40"><use xlink:href="#icon_'+ i +'"/></svg>';
     }
    
     //リンクに処理結果を設定
     document.querySelector("a").href 
      = "data:image/svg+xml," + encodeURIComponent('<?xml version="1.0" encoding="utf-8"?>' + div.innerHTML);
    };
      </script>
     </body>
    </html>

    0 件のコメント:

    コメントを投稿