2014年3月3日月曜日

svgをプログラム言語としてとらえると-svgエチケット論

web上でベクタグラフィックを描けるのは非常に便利であるものの,使い方によっては薬にも毒にもなるよという話.長いので,すっ飛ばして結論だけ読んでもよし.
※例によって個人調べなので,多分に間違いが含まれているものと思われます…

2014/3/4 ちょっと改訂
2014/3/5 補足を追加
2014/3/6 optimizeSpeedについての記述を追加
2014/3/19 strokeについての記述を追加




昨今webブラウザ上でベクタグラフィックを描くことができることからsvgが脚光を浴びるようになりました.現状ではそれほど利用率が高くないものの,今後様々な用途で広く使われていくことが予想されます.

さて,このように非常に便利なsvgですが,大抵はjpgやpng形式などのラスタ形式と比較することで紹介されることも多く,svgを単なるベクタ画像形式の一つと理解されている方も多かろうと思います.しかし見方を変えるとそれ以上に多くの考慮すべき点が浮かび上がってきます.

プログラム言語としてのsvg


html5においてsvgに似た機能を提供するものとしてcanvas要素があります.canvas要素にはグラフィックを描くためのapiが定められており,これをスクリプトから操作することでグラフィックを描きます.

一方のsvgでは「画家のモデル」と呼ばれる方法でグラフィクを描いていきます.つまりxmlツリーとして記述されたグラフィックの描画指示を上から順に実行し,目的のグラフィックを得ているのです.
まとめると,次のようになります.

  • canvas要素…javascriptの文法を使ってグラフィックを描く
  • svg…xmlの文法を使ってグラフィックを描く

つまり,canvas要素もsvgも基本的には同じものとも言えるのです.実際svgにもjavascriptのfunctionに相当する仕組みはdefs-use要素として用意されていますから,ちょっと乱暴ですがsvgを画像プログラム(スクリプト)言語と言ってもそれほどおかしくないのです.

※確かに事前に提供されているapiの差から得意とする分野は別れますが.
※話はずれますが,canvas要素でもsvgのような動作を実現することは可能です.
※この類推から得られる結論としては次のようなものが挙げられます.
  • svg文書はソースコードであり,環境毎にコンパイルすると最適なラスタ画像が得られる.
  • svgをjavascriptで操作するというのは「ソースコードをマクロプログラムで自動生成している」ということである
※svgに限らず,ベクタ形式の画像では多かれ少なかれグラフィックの描画処理が発生します.しかしsvgが決定的に異なるのは「ユーザーが自由に作り込める」点にあります.

    良いsvg・悪いsvg


    さてsvgにプログラム言語的な側面があることが判ったら,次の事を考えてみましょう.
    一般にプログラムというものは何らかの目的を果たすために作られるものです.ですがその実現には様々な方法が考えれます.
    ※「html文書を表示する」と言った目的に対し,firefoxやchromeといった様々な選択肢がるといったようにです.

    すると,自ずと「良いプログラム」「悪いプログラム」と言ったものが見えてきます.何れも同じ目的を果たせるのですが,実際にそれを使う場面(ユースケース)に応じて優劣が発生するのです.例えば軽い・重い,判りやすい・判りにくい,使いやすい・使いにくい等.
    ※いわゆる非機能要件ってやつです.

    例えばsvgでドット絵を表現する例で言えば,
    • 1画素に1rect要素を割り当てる
    • 1色に1path要素を割り当てる
    といった二つの方法が考えられます.この時,前者は記述がシンプルであるものの描画処理に時間がかかり,後者はパス定義が複雑となる一方で描画処理が軽快となります.従ってメンテナンス性を取るか,描画処理時間を取るかで優劣が逆転します.

    svgをwebで利用する上での「良いsvg」とは


    では今後需要が高まると予想される「web上でsvgを使う」という場合においては,どのようなsvgが求められるのでしょうか?何より次の点が挙げられるでしょう.

    • 動作が軽快なsvg

    webブラウザがsvgファイルを読み込むと,その内容に従ってグラフィックを描いていきます.要はプログラムが実行されるわけですが,いかな素晴らしいグラフィックであるとしても,その処理に時間が掛かるようでは意味は無いでしょう.

    また,無駄な処理が多ければそれだけ無駄に電力を消費します.モバイル機器が主流となりつつある今,「あのサイトを開いていると,なんだかわからないけれどもりもりバッテリー残量が減っていく」と言った悪評判がたてばビジネスチャンスを逃す結果となりかねません.

    つまり,svgの節度ある活用が今後web上でsvgを利用する際のエチケットとして定着することが有り得るわけです.

    ※この問題はsvg(1.1)が必ずしもweb上でのみ動作しうるものとして設計されたわけでないことに起因しています.(流石にユースケースとしては想定していたと思われますが)
    svg2以降ではhtml5と連携しうることを前提に,より使いやすいものとなっていくことでしょう.

    svgのパフォーマンスチューニングの勘所


    では軽快なsvgとするにはどうすればよいのでしょう?
    以下筆者の経験です.いずれもsvgを画像ファイルと捉えただけでは気付きにくいものばかりです.

    ※いずれも通常の利用においては意識せずともよいのですが,svgを仕組みを知ると無闇に使いたくなるものです.ですから,svgを作成する際はこれらの注意点を念頭に置いて危ないなと感じたら引き返す余裕を持ちましょう.
    ※いかなプログラム言語とは言え,チューニングの方法論は一般的な手続き型言語とは異なります.内部処理をきちんと把握しなければならない点はある意味sqlにおけるそれに似ていると言えます.
    ※もちろん,きちんとした目的がある場合はこの限りではありませんが,単なる静的なアイコン・ロゴの表示に大きな負荷が掛かってしまうのはいかにも無駄と感じざるを得ません.
    ※先頭ほど注意すべきもので,下に行くほど優先度は低くなります.

    ・要素数を極力減らす

    大雑把には1要素1手順と言ってよいです.従ってこの数が増えるほど処理時間が増えていきます.それ以外にもsvgファイルの増大によるネットワークトラフィックへの負荷,dom展開の手間を考えるとできるだけ要素の数は減らしたほうが良いと言えます.
    従って,同様の設定が為されているrectやcircle要素等が多数存在するのであれば,単一のpath要素に複合パスとしてまとめることが出来ないか検討しましょう.
    また過剰な演出はそれだけで多量の要素を消費することになります.svgでは出来る限り見た目にもコード的にもシンプルな方が望ましいと言えます.
    ※とは言え,2〜3個の要素であればそれほど問題はありません.むしろ,グラフデータや地図データ等の外部リソースからsvgを自動生成した際に,細切れの要素が大量に作られてしまうケースに有効なテクニックです.
    ※極論すればillustratorやinkscapeが悲鳴を上げるようなベクタ画像がwebブラウザで使えるわけがないのです.

    ・コンテナ要素の利用を減らす

    コンテナ要素とはsvg,g,use,marker,clipPath,mask要素等を指します.これらは大元のsvg要素が生成するメインカンバスの他に暗黙のカンバスを生成するため,その分処理が増加します.とりわけmarker要素はマーカーの描画毎に処理が発生するため,見た目の要素数以上に処理速度を低下させます.
    svgを手書きする際は,これらの要素を過剰に使わないように注意し,場合によっては先ほどと同様に単一のpathで置き換えられないか検討します.
    ※もちろん,web以外の用途(例えばpng画像自動生成のソースコードとしての利用)であればむしろコードのメンテナンス性が優先されますからガンガン使って良いです.

    ・過剰な画像フィルタを掛けない

    filter要素による画像処理はラスタライズ後のグラフィックに対してピクセル操作を施すことに他なりません.従って過剰な原始フィルタの掛けあわせは厳に控えるべきです.また大きな画像にフィルタを掛ける行為はそれだけで絶大なパフォーマンス低下を招きます.
    同じ効果が得られる別の方法はないか検討して下さい.例えばpattern要素を使ってパターン敷き詰めを行った図形にfilterを掛けるよりも,パターン図形にfilterを掛けた上で敷き詰めを行ったほうが処理が軽くなります.
    ※巨大なcanvas要素が重いのと同様です.
    ※どうしてもその効果が必要であれば事前にpng画像としてラスタライズしておき,image要素から読み込ませるようにします.

    ・パスの複雑度を下げる

    webでsvgを使う場合,それほど高精細なデータは必要無いはずです.従って過剰に頂点を持つpathを定義するより,見た目でわからない程度に頂点を減らしたり,3次ベジェ曲線を2次ベジェ曲線や直線で近似するようにしてパス文字列をシンプルにしましょう.
    また頂点座標の小数点桁数を減らすことでsvgファイルサイズを抑える事が出来ます.
    ※画像を拡大可能とする場合には適しません.

    ・複雑なstrokeを避ける

    strokeによる線の描画は,元となる図形の境界から更に描画範囲を求める必要があるため,その分処理が煩雑になります.また,stroke-dasharrayによる破線の描画,stroke-linejoin,stroke-linecapsによる端点の丸め処理等も,パスの長さや破線の細かさに比例し処理が重くなります.
    そのため,stroke仕様の裏をかくようなトリッキーなグラフィック描画はweb環境においては避けたほうが良いと言えます.
    ※stroke-dasharray値を極端に小さくするようなものがこれに相当します.なお,一般的な利用であればそれほど気にする必要はないかも知れません.

    ・viewBox範囲外に画像要素を配置しない

    描画されない要素は無駄と言えます.処理上必要な場合であっても「display:none」として描画処理をスキップさせるようにしましょう.
    ※svgをパンさせて描画範囲を移動させるようなケースではこの限りではありません.が,工夫次第で処理速度を向上させることが出来るとも言えます.

    ・アニメーションを行わない

    (smil/dom/css)アニメーションは必要以上にsvgの再描画を発生させる原因となります.出来ることなら静的なグラフィックとしましょう.
    ※一度描画したら,極力触らないのが鉄則です.

    ・一部をラスタ画像に置き換える

    全てをベクタデータとして描くのではなく,image要素を使って一部をラスタ画像に置き換えます.多量の要素があるということはそれだけ細かい画像と言えるため,ラスタ画像に置き換えても見た目上問題は起こり難い筈です.xlink:href属性にdataスキーム形式で画像を埋め込んでしまえば(ファイルサイズこそ増大しますが) 単一のsvgファイルとして扱うことが出来ます.
    ※svg2では待望のcanvas要素をsvg内部で直接扱えるようになります.従ってグラフィックの役割毎に処理を切り替えると言った使い分けが容易となるでしょう.

    ・style要素によるスタイルの指定を控える

    セレクタによる暗黙のスタイル設定処理が発生するため,処理速度に影響します.特に擬似クラス等のtree構造に関わるものは利用しないほうが良いでしょう.
    ※なおstyle要素を使うことでsvgファイルのサイズが小さくなるケースもあるため,一概にこうすべきというわけではありません.

    ・ディスプレイに表示される画像のサイズを小さくする

    いかなsvgとは言え,無闇に大きな領域を専有するのは処理速度に影響します.また,画像サイズを小さくすることで副次的にパスの簡略化が行えるようになります.
    ※svgのwidth,height属性と言ったものは本質的に意味はなく,結局のところどの位のサイズのカンバスにグラフィックが描画されるかが重要となります.ですが,実用上それほど意識する必要はないかも知れません.

    ・余計なデータを削除する

    inkscapeやillustratorなどを使ってsvgを生成した場合,webブラウザでは必要のない情報(メタデータ)が多数含まれています.dom展開処理に影響するため予めコードから削除しておきましょう.
    ※なお,注釈やタイトル等のsvgのアクセシビリティに関わる要素・属性はsvgがxmlであることの利便性を損なう可能性があるため削除しないほうが良いでしょう.

    ・buffered-rendering属性を活用する

    http://www.w3.org/TR/2014/WD-SVG2-20140211/painting.html#BufferedRendering
    次期svg2ではbufferd-rendering属性を使ってグラフィックの描画内容をキャッシュさせることが可能となります.現状では本プロパティをサポートする環境がpresto(旧opera,現operaはblink)のみと,使い勝手が悪いですが,将来を見据えて今からこっそりと記述しておくのも悪くありません.

    ・描画品質をoptimizeSpeedとする

    図形や文字列を描画する際,通常暗黙のうちにアンチエイリアス処理が為されますが,この処理の有無はコード作成者側で制御できます.下記の属性を定義し,値「optimizeSpeed」を設定すると,仕様の上では描画処理を高速化(つまり処理のスキップ)させる事が出来ます.
    • shape-rendering
    • image-rendering
    • text-rendering
    • color-rendering
    ですが見た目が汚くなったり,環境によっては効果がないものもあるため,あくまで補助的なものとして使って下さい.

    ・DOMのパフォーマンスチューニングテクニックも併用する

    javascriptと組み合わせる場合,一般的なhtmlやxmlで行われている高速化テクニックはsvgにおいても有効です. 代表的なものとしては,
    • ノードの検索にはより具体的な条件を付ける(getElementByIdを使う)
    • ノードの検索結果はキャッシュして使いまわす
    • ノードの生成にはcreateElementNSよりcloneNodeを用いる
    • ノードの廃棄は最小限に止め,出来ることなら再利用する
    • ノードの一括追加にはdocumentFragmentオブジェクトを使う
    • getAttribute,setAttributeよりも専用のapiがあればそちらを使う.(hrefプロパティ等)
    • style等のアクティブオブジェクトは内部でキャッシュして使いまわす
    等が挙げられます.

    ※プログラミングの経験者の方であれば,svgの仕様書を読んで自分ならどう組むかを考えてみて下さい.難しそうだな,面倒だなと思った部分は大抵処理が重い部分です.

    ※コードを短くするのが目的というのであればこちらも参考になるかと.
    Advent Calendar 3日目:SVG画像を1バイトでも削るためのコードゴルフ
    http://d.hatena.ne.jp/rikuo/20131203
    短いsvgコードはそれだけでそれだけでも価値があります.

    もうひとつの解決策


    上記の対策を行うとなるとどうしても画像がシンプルとなりがちです.そうは行っても豪華なグラフィックを使いたい!というのであれば次の方法が考えられます.

    ・canvas要素を使ってsvgをラスタライズしブラウザにキャッシュさせる

    現行のブラウザであればまずcanvas要素をサポートしているはずなので,ここにsvgの内容を(img要素を使って)描画し,その処理結果をブラウザにキャッシュさせるのです.html5のweb strage機能を使えば何とかなる気がします.
    ※あくまで理屈上です.仕組みは考えねばなりません.

    ・思い切ってsvgの利用を諦める(身も蓋もねー)

    予めsvgをpng画像に変換しておくのです.
    そんなの面倒!と思われるかも知れませんが,事前のpng変換処理でその後の数千数万台の端末で行われるであろうラスタライズ処理が浮く事を考えると,検討するに値します.メディアクエリと組み合わせて考えましょう.また,下のような仕組みも役に立つと思われます.
    An HTML extension for adaptive images
    http://www.w3.org/html/wg/drafts/srcset/w3c-srcset/

    まとめ


    • svgはプログラム言語である
    • よって使い方により良いsvg,悪いsvgというものが考えられる
    • 「webでsvgを使う」場合において悪いsvgとは処理が重いsvgである
    • 重いsvgをばらまく行為はエチケットとして慎んだほうが良い
    • 従ってwebでは基本的にシンプルな構造で短いsvgを使いたい 

    補足

    • 将来的にgpuによる描画支援がsvgにも適用されるのであれば,それほど気にしなくても良いのでは?
      →いいえ,如何に表示がスムーズに行えるようになったからと言って無駄な処理が無くなるわけではなく,単に許容できる処理の量が増えるだけです.ですから,svgのコードを軽いものに保つという行為の必要性は変化しません.
      但し,優先順位が変わると言った事は考えられます.
    • svgによる描画処理を最適化するためには冗長なコードとなっても仕方がない.
      →必ずしもそうとは言えません.
      実はここが難しいところで,ブラウザに優しいsvgは必ずしもネットワーク(転送量)や人に優しい(可読性等)わけではなく,このさじ加減については筆者にもわかりません.
      この3つの要素を同時に充たすことは不可能なので,svgを導入したいwebサイトの特性に応じ個別に検討したほうが良いでしょう.例えば,
      • svgの処理…アクセス数にlinear(その都度実行される)
      • ネットワークの転送量…端末数にlinear(キャッシュを考慮した場合)svgzを使う手もある
      • 人の作業量…コンテンツ量にlinear
      と言った特性を元に最適なチューニングを行うことが考えられます.
    • ここでは「ユーザビリティー」とか「アクセシビリティー」と言ったものについては全く考慮していない点に注意して下さい.今後別の観点での方法論が出てくることが期待されます.

    0 件のコメント:

    コメントを投稿