Kekeの日記

エンジニア、読書なんでも

SVGのsymbol、use要素とviewBox属性の挙動

本記事

私は数ヶ月ほど前に、友人の間で作っていた個別プロジェクトのAIエンジン、インフラ、サーバーサイド、フロントエンドを担当していました。

プロダクトでは人工知能(特にニューラルネットワーク:NN)を考えていたため、プロジェクトの紹介ページでは以下のようなアニメーションを作成して載せました。アップロードする際に画質を落としたので、少し見辛いかもしれません。

このホームページはVue.jsで書かれています。

f:id:bobchan1915:20181011152432g:plain

また、個人的に街並みのアニメーションも欲しくて、以下のようなアニメーションも作成して組み込みました。

サイトに長くいて欲しいので、飛行機だったり、速度オーバーする車、それを取りしまる車など、かなり盛り込みました。また、家や風車の速度を調整して遠近感を出してみるなど、それなりに工夫をしました。

f:id:bobchan1915:20181011152639g:plain

このようなアニメーションを作成するにあたって、例えば

  • NNの隠れ層数
  • NNのノード数

などを自動生成で対応できるAPIを作りました。いつか公開しようかなと思います。

この中で、特にsymboluse要素とviewBox属性の挙動が非常に理解しづらいことを感じて、頭の中を整理するためにも記事にしようと思いました。

目次

今回の登場人物 の解説

symbol要素とは

次に説明するuse要素と合わせてSVGでオブジェクトをグループ化して参照するための要素です。

<symbol>要素はグループとして要素をまとめるためのものです。同様の役割を果たす<g>要素がいますが、<symbol>要素で囲まれたものは何も表示されません。

しかしながら、あらかじめ宣言するという構造上<defs>要素に<g>要素と同様に格納するのが慣習となっているみたいです。

ここでは節を設けていませんが<defs>タグというものは開始タグと終了タグの間の要素を表示しません。

また<g>要素と違うところは以下の通りです。

  • viewBox属性を持てる
  • preserveAspectRatio属性を持てる

ここで実際に書いてみようと思います。

width="4cm" height="4cm" を指定してviewPortを作ります。この領域ではSVGが描画されます

f:id:bobchan1915:20181011172351p:plain

まずは円がかけることを確認します。

<svg width="4cm" height="4cm" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background: pink">
 <circle cx="200" cy="200" r="50" fill="#fedf51"/>
</svg>

f:id:bobchan1915:20181011171952p:plain

そして次に<defs><symbols>要素を使ってみます。

<svg width="4cm" height="4cm" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background: pink">
        <defs>
            <symbol id="circle">
                    <circle cx="200" cy="200" r="50" fill="#fedf51"/>
            </symbol>
        </defs>
        <use xlink:href="#circle" x="0" y="0"/>
    </svg>

すると案の定、何も表示されません。これと使うには次節の<use>要素が必要となります。

f:id:bobchan1915:20181011172037p:plain

use要素とは

もちろん<defs><g>でするパターンと<symbol>でするパターンには明確な理由があります。

それはモジュール化して、再利用できて、拡張性を高くするためです。

しかし、どのようにして宣言した要素を使うことができるのでしょうか。ここで<use>要素を使います。

以下のようにxlink:href属性でURIを再利用したいグループやシンボルを指定し、どこに使うか(useするか)をxyで定義します。

またtraslateプレゼンテーション属性を使うことによってさらに変換をかけることができます。

ここでuse使っていきます。

<use xlink:href="#circle" x="0" y="0"/>

とすると以下のようになります。

f:id:bobchan1915:20181011171952p:plain

ここでxyを指定したのですが、これは

useするものを一つの系と捉え、use先からの相対座標を決めます。

つまり、この通りです。

f:id:bobchan1915:20181012030034p:plain

もともとはsymbolはviewPortという絶対座標系で書かれていたので、それをモジュール化してuseすると、そのxyは相対距離を入れるので、viewPort内に収まっているか収まっていないかの問題はおいておいて、中の要素自体が変わるわけではありません

viewBox属性とは

viewBox属性とは子要素を描画する際に拡大したり、縮小したり、移動などができるものです。

指定の方法は以下の通りです。

viewBox="x, y, width, height"

デフォルトではpreserveAspectRatioが有効になっています。

xとyについて

まず、半径がviewPortの半分の円を書きます。

f:id:bobchan1915:20181012020857p:plain

このときwidth="4cm" height="4cm" viewBox="0 0 400 400"なので以下のようになっています。

f:id:bobchan1915:20181012021055p:plain

ここで以下のようにします。

width="4cm" height="4cm" viewBox="200 0 400 400"とすると以下のようになります。

f:id:bobchan1915:20181012021619p:plain

ここからviewBoxがわからなくなる人が多いと思うのですが、viewBox属性は子要素のために施すためのものと覚えれば大丈夫です。

  • ①のようにviewBoxはviewBox="0 0 400 400"から`viewBox="200 0 400 400"へと変わりました。

  • ②のように今まで通り円が親要素の座標系で描かれます

f:id:bobchan1915:20181012021959p:plain

しかしながら、この要素はviewBox内で見えるものを親要素に移すような働きをするので、結果として以下のようになります。

f:id:bobchan1915:20181012022151p:plain

他にもやってみましょう。

viewBox="0 200 400 400"は以下の通りです。

f:id:bobchan1915:20181012022259p:plain

heightとwidthについて

次はheightweightを変えてみましょう。

たとえばviewBox="200 0 800 800"をしてみます。

f:id:bobchan1915:20181012022603p:plain

なぜこのようになるのでしょうか。

実は以下のような流れになっています。

f:id:bobchan1915:20181012022826p:plain

明文化するとviewBoxでwidthとheight`を指定するとその大きさに座標変換をかけるがしっくりくると思います。

viewBox="0 0 400 400"に対してviewBox="0 800 800"は全体が800,800にしないといけないので400,400でみれていた円は400/800, 400/800ずつそれぞれの座標変換されます。

これを知るとviewBox="0 0 200 200"は少し簡単かもしれません。

400, 400でみられていた円を、全体が200, 200でみたいので、当然円は2倍大きくみえます。座標も100にあったものはviewPortが絶対座標だと考えると200に見えるようになります。

つまりviewBox="0 0 400 400"はこのようになっています。

f:id:bobchan1915:20181012025314p:plain

そしてviewBox="0 0 800 800"

f:id:bobchan1915:20181012025340p:plain

viewBox="0 0 200 200"は以下の通りです。

f:id:bobchan1915:20181012025407p:plain

まとめると

  • 一旦はviewBoxの大きさでみてみる
  • それをviewPortの大きさに収めようとしたらどうなるかを考える

xとyとheightとwidthについて

落ち着いて考えると理解できるようになりました。

たとえばviewBox="200 200 400 400"のようなケースです。

これも今までならったようにviewBoxを移動させて

f:id:bobchan1915:20181013093046p:plain

どのようにviewPortに拡大・縮小されるかを考えて

f:id:bobchan1915:20181013093116p:plain

するとこのようになり、思ったような挙動になります。

f:id:bobchan1915:20181013093156p:plain

symbolとviewBox

先ほどのsymbolのセクションで

symbolも同様にviewBoxを持つことができる

と説明しました。ただでさえ、理解の難しかったviewBoxに、さらにviewBoxが入ってきたらどうなるでしょう。

まず最初に何も指定していない以下のような円を定義します。

<svg width="4cm" height="4cm" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background: pink">
        <defs>
            <symbol id="circle">
                    <circle cx="200" cy="200" r="200" fill="#fedf51"/>
            </symbol>
        </defs>
        <use xlink:href="#circle"/>
    </svg>

これは以下のような円が描画されます。

f:id:bobchan1915:20181013090517p:plain

ここのsymbol要素にviewBoxを指定してみましょう。

例えば以下のようにします。

<svg width="4cm" height="4cm" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background: pink">
        <defs>
            <symbol id="circle" viewBox="0 0 400 400">
                    <circle cx="200" cy="200" r="200" fill="#fedf51"/>
            </symbol>
        </defs>
        <use xlink:href="#circle"/>
    </svg>

すると以下のようになります。どうなっているのかは知りませんが、何も変わらなそうなのはイメージできます。

f:id:bobchan1915:20181013090730p:plain

そして、これを`viewBox="0 0 800 800"に変えてみると以下のようになります。

f:id:bobchan1915:20181013090851p:plain

また、もとのsvg要素のviewBoxも変更します。

<svg width="4cm" height="4cm" viewBox="0 0 800 800" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background: pink">
        <defs>
            <symbol id="circle" viewBox="0 0 800 800">
                    <circle cx="200" cy="200" r="200" fill="#fedf51"/>
            </symbol>
        </defs>
        <use xlink:href="#circle"/>
    </svg>

何も起きません。

f:id:bobchan1915:20181013090851p:plain

では、svg要素だけ変更してsymbol要素を変更しなかったらどうなるのか。

<svg width="4cm" height="4cm" viewBox="0 0 800 800" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background: pink">
        <defs>
            <symbol id="circle" viewBox="0 0 400 400">
                    <circle cx="200" cy="200" r="200" fill="#fedf51"/>
            </symbol>
        </defs>
        <use xlink:href="#circle"/>
    </svg>

すると以下のようになります。

f:id:bobchan1915:20181013091447p:plain

まとめると

  • viewBoxは描画される際に適用されるものなので、重複してviewBoxheigthwidthに適用されることはない。
  • しかし並進変換viewBox="200 0 800 800"のようなxyを指定すると同様に影響する

たとえば以下の通りです。

<svg width="4cm" height="4cm" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background: pink">
        <defs>
            <symbol id="circle" viewBox="0 0 200 200">
                    <circle cx="200" cy="200" r="200" fill="#fedf51"/>
            </symbol>
        </defs>
        <use xlink:href="#circle"/>
    </svg>