やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

一つの矢印から複数パターンのSVGスプライトを作成する

 アフィン変換で図形パターンを量産する。

前回まで

今回

 前回作成した矢印を流用して複数の矢印図形を作ってみる。

成果物

  • インライン版
  • 外部参照版

インライン版

 サーバ不要。

index.html

<meta charset="UTF-8">
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
<defs>
<symbol id="arrow-top" viewBox="0 0 256 256">
<path style="opacity:1;fill:none;fill-opacity:0;stroke:currentColor;stroke-width:16;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M128 7.781 7.781 120.22H64V248h128V120.219h56.219L128 7.78z"/>
</symbol>
<symbol id="arrow-bottom" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(180, 128, 128)"></use></symbol>
<symbol id="arrow-left" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(270, 128, 128)"></use></symbol>
<symbol id="arrow-right" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(90, 128, 128)"></use></symbol>
<symbol id="arrow-top-left" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(315, 128, 128)"></use></symbol>
<symbol id="arrow-top-right" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(45, 128, 128)"></use></symbol>
<symbol id="arrow-bottom-left" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(225, 128, 128)"></use></symbol>
<symbol id="arrow-bottom-right" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(135, 128, 128)"></use></symbol>
<symbol id="arrow-top-bottom" viewBox="0 0 256 256">
    <use href="#arrow-top" transform=""></use>
    <use href="#arrow-bottom" transform=""></use>
</symbol>
<symbol id="arrow-pair-top-bottom" viewBox="0 0 256 256">
    <use href="#arrow-top" transform="scale(0.5, 1)"></use>
    <use href="#arrow-bottom" transform="scale(0.5, 1) translate(256, 0)"></use>
</symbol>
<symbol id="arrow-pair-left-right" viewBox="0 0 256 256">
    <use href="#arrow-left" transform="scale(1, 0.5)"></use>
    <use href="#arrow-right" transform="scale(1, 0.5) translate(0, 256)"></use>
</symbol>
<symbol id="arrow-quad-outside" viewBox="0 0 256 256">
    <use href="#arrow-top-left" transform="scale(0.5, 0.5)"></use>
    <use href="#arrow-top-right" transform="scale(0.5, 0.5) translate(256, 0)"></use>
    <use href="#arrow-bottom-left" transform="scale(0.5, 0.5) translate(0, 256)"></use>
    <use href="#arrow-bottom-right" transform="scale(0.5, 0.5) translate(256, 256)"></use>
</symbol>
<symbol id="arrow-quad-inside" viewBox="0 0 256 256">
    <use href="#arrow-bottom-right" transform="scale(0.5, 0.5)"></use>
    <use href="#arrow-top-right" transform="scale(0.5, 0.5) translate(0, 256)"></use>
    <use href="#arrow-bottom-left" transform="scale(0.5, 0.5) translate(256, 0)"></use>
    <use href="#arrow-top-left" transform="scale(0.5, 0.5) translate(256, 256)"></use>
</symbol>
</defs>
</svg>

<style>
.icon{width:1em; height:1em;}
</style>

<svg class="icon"><use href="#arrow-top"></use></svg>
<svg class="icon"><use href="#arrow-bottom"></use></svg>
<svg class="icon"><use href="#arrow-left"></use></svg>
<svg class="icon"><use href="#arrow-right"></use></svg>
<svg class="icon"><use href="#arrow-top-left"></use></svg>
<svg class="icon"><use href="#arrow-top-right"></use></svg>
<svg class="icon"><use href="#arrow-bottom-left"></use></svg>
<svg class="icon"><use href="#arrow-bottom-right"></use></svg>

<svg class="icon"><use href="#arrow-pair-top-bottom"></use></svg>
<svg class="icon"><use href="#arrow-pair-left-right"></use></svg>
<svg class="icon"><use href="#arrow-quad-outside"></use></svg>
<svg class="icon"><use href="#arrow-quad-inside"></use></svg>

<svg class="icon"><use href="#arrow-top" transform="rotate(15, 8, 8)" fill="red"></use></svg>

<svg class="icon"><use href="#arrow-top" style="color:red;"></use></svg>
<svg class="icon"><use href="#arrow-top" style="color:green;"></use></svg>
<svg class="icon"><use href="#arrow-top" style="color:blue;"></use></svg>
<svg class="icon"><use href="#arrow-top" style="color:yellow;"></use></svg>
<svg class="icon"><use href="#arrow-top" style="color:magenta;"></use></svg>
<svg class="icon"><use href="#arrow-top" style="color:cyan;"></use></svg>

<svg width="2em" height="2em"><use href="#arrow-top"></use></svg>
<svg width="3em" height="3em"><use href="#arrow-top"></use></svg>
<svg width="4em" height="4em"><use href="#arrow-top"></use></svg>
<svg width="5em" height="5em"><use href="#arrow-top"></use></svg>

外部参照版

arrows.svg

<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
<defs>
<symbol id="arrow-top" viewBox="0 0 256 256">
<path style="opacity:1;fill:none;fill-opacity:0;stroke:currentColor;stroke-width:16;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M128 7.781 7.781 120.22H64V248h128V120.219h56.219L128 7.78z"/>
</symbol>
<symbol id="arrow-bottom" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(180, 128, 128)"></use></symbol>
<symbol id="arrow-left" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(270, 128, 128)"></use></symbol>
<symbol id="arrow-right" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(90, 128, 128)"></use></symbol>
<symbol id="arrow-top-left" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(315, 128, 128)"></use></symbol>
<symbol id="arrow-top-right" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(45, 128, 128)"></use></symbol>
<symbol id="arrow-bottom-left" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(225, 128, 128)"></use></symbol>
<symbol id="arrow-bottom-right" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(135, 128, 128)"></use></symbol>
<symbol id="arrow-top-bottom" viewBox="0 0 256 256">
    <use href="#arrow-top" transform=""></use>
    <use href="#arrow-bottom" transform=""></use>
</symbol>
<symbol id="arrow-pair-top-bottom" viewBox="0 0 256 256">
    <use href="#arrow-top" transform="scale(0.5, 1)"></use>
    <use href="#arrow-bottom" transform="scale(0.5, 1) translate(256, 0)"></use>
</symbol>
<symbol id="arrow-pair-left-right" viewBox="0 0 256 256">
    <use href="#arrow-left" transform="scale(1, 0.5)"></use>
    <use href="#arrow-right" transform="scale(1, 0.5) translate(0, 256)"></use>
</symbol>
<symbol id="arrow-quad-outside" viewBox="0 0 256 256">
    <use href="#arrow-top-left" transform="scale(0.5, 0.5)"></use>
    <use href="#arrow-top-right" transform="scale(0.5, 0.5) translate(256, 0)"></use>
    <use href="#arrow-bottom-left" transform="scale(0.5, 0.5) translate(0, 256)"></use>
    <use href="#arrow-bottom-right" transform="scale(0.5, 0.5) translate(256, 256)"></use>
</symbol>
<symbol id="arrow-quad-inside" viewBox="0 0 256 256">
    <use href="#arrow-bottom-right" transform="scale(0.5, 0.5)"></use>
    <use href="#arrow-top-right" transform="scale(0.5, 0.5) translate(0, 256)"></use>
    <use href="#arrow-bottom-left" transform="scale(0.5, 0.5) translate(256, 0)"></use>
    <use href="#arrow-top-left" transform="scale(0.5, 0.5) translate(256, 256)"></use>
</symbol>
</defs>
</svg>

 パスデータがあるのは最初の図形id="arrow-top"だけ。他はそれを<use>で参照し、transformでアフィン変換したものを流用しているだけ。

index.html

<meta charset="UTF-8">
<style>
.icon{width:1em; height:1em;}
</style>

<svg class="icon"><use href="./arrows.svg#arrow-top"></use></svg>
<svg class="icon"><use href="./arrows.svg#arrow-bottom"></use></svg>
<svg class="icon"><use href="./arrows.svg#arrow-left"></use></svg>
<svg class="icon"><use href="./arrows.svg#arrow-right"></use></svg>
<svg class="icon"><use href="./arrows.svg#arrow-top-left"></use></svg>
<svg class="icon"><use href="./arrows.svg#arrow-top-right"></use></svg>
<svg class="icon"><use href="./arrows.svg#arrow-bottom-left"></use></svg>
<svg class="icon"><use href="./arrows.svg#arrow-bottom-right"></use></svg>

<svg class="icon"><use href="./arrows.svg#arrow-pair-top-bottom"></use></svg>
<svg class="icon"><use href="./arrows.svg#arrow-pair-left-right"></use></svg>
<svg class="icon"><use href="./arrows.svg#arrow-quad-outside"></use></svg>
<svg class="icon"><use href="./arrows.svg#arrow-quad-inside"></use></svg>

<svg class="icon"><use href="./arrows.svg#arrow-top" transform="rotate(15, 8, 8)" fill="red"></use></svg>

<svg class="icon"><use href="./arrows.svg#arrow-top" style="color:red;"></use></svg>
<svg class="icon"><use href="./arrows.svg#arrow-top" style="color:green;"></use></svg>
<svg class="icon"><use href="./arrows.svg#arrow-top" style="color:blue;"></use></svg>
<svg class="icon"><use href="./arrows.svg#arrow-top" style="color:yellow;"></use></svg>
<svg class="icon"><use href="./arrows.svg#arrow-top" style="color:magenta;"></use></svg>
<svg class="icon"><use href="./arrows.svg#arrow-top" style="color:cyan;"></use></svg>

<svg width="2em" height="2em"><use href="./arrows.svg#arrow-top"></use></svg>
<svg width="3em" height="3em"><use href="./arrows.svg#arrow-top"></use></svg>
<svg width="4em" height="4em"><use href="./arrows.svg#arrow-top"></use></svg>
<svg width="5em" height="5em"><use href="./arrows.svg#arrow-top"></use></svg>

HTTPS server

解説

 SVGスプライトを作る。フォーマットは以下。

<svg>
<defs>
  <symbol id="...">
    <path d="..."/>
  </symbol>
</defs>

 元となる矢印の図形はInkscapeで矢印を描くの成果物を使う。すると以下のようになる。

<symbol id="arrow-top" viewBox="0 0 256 256">
<path style="opacity:1;fill:none;fill-opacity:0;stroke:currentColor;stroke-width:16;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M128 7.781 7.781 120.22H64V248h128V120.219h56.219L128 7.78z"/>
</symbol>

 あとは上記をアフィン変換して流用する。上記は上向きの矢印だが、反対に下向きにするため回転させる。

<symbol id="arrow-bottom" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(180, 128, 128)"></use></symbol>

 ポイントはtransform属性値。ここにrotate(角度, 中点X, 中点Y)を入力する。角度は0360、中点は回転する中心点をXとYの座標で指定する。単位はpx(ピクセル)。今回の図形はviewBoxにある通り幅と高さがそれぞれ256px。画面中央で回転させたいから、(128, 128)を中点として指定した。transform="rotate(180, 128, 128)"になる。

 あとはこれを上下左右斜めの八方向だけ用意する。

 複数の図形を合わせたパターンもある。以下は上向きと下向きの矢印を半分のサイズにして一つの図形にしたもの。

<symbol id="arrow-pair-top-bottom" viewBox="0 0 256 256">
    <use href="#arrow-top" transform="scale(0.5, 1)"></use>
    <use href="#arrow-bottom" transform="scale(0.5, 1) translate(256, 0)"></use>
</symbol>

 四つ合わせて以下のようなものも作れる。

<symbol id="arrow-quad-outside" viewBox="0 0 256 256">
    <use href="#arrow-top-left" transform="scale(0.5, 0.5)"></use>
    <use href="#arrow-top-right" transform="scale(0.5, 0.5) translate(256, 0)"></use>
    <use href="#arrow-bottom-left" transform="scale(0.5, 0.5) translate(0, 256)"></use>
    <use href="#arrow-bottom-right" transform="scale(0.5, 0.5) translate(256, 256)"></use>
</symbol>

 どこまで変更できるのか。じつは以下のような制約がある。

  • <path>ですでに設定済みのスタイルは変更不可
use要素の振る舞い

use要素に設定したスタイル属性値はコピー元の図形に値が設定されていない時のみ有効となる

svg要素の基本的な使い方まとめ

 よって上記の場合、fillstroke等は<use>で変更できない。すでに定義済みのスタイルだから。

 逆にwidth,height,transform等は未定義のため変更可能。

 また、stroke:currentColorにしているため、CSScolor属性値と同じ値になる。よってCSSで色の変更が可能である。以下は赤色に変更したもの。

<svg class="icon"><use href="#arrow-top" style="color:red;"></use></svg>

 こうした抜け道を使えば単色なら変更可能だ。細かく制御したいなら<path>class属性値等を駆使することになりそう。

 サイズに関しては簡単だ。<use>する側の<svg>width,heightを指定するだけ。

<svg width="5em" height="5em"><use href="#arrow-top"></use></svg>

 ただ、定義側で<symbol>毎にviewBoxを定義するのが冗長に見える。でもこれを省略したり、親の<svg><defs>だけに指定しても正しく表示されなかった。毎回<symbol>viewBoxをセットする必要があるようだ。

 どうせ全部同値のviewBox="0 0 256 256"なのだから一度だけ書いて済ませたかった……。

...
<symbol id="arrow-bottom" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(180, 128, 128)"></use></symbol>
<symbol id="arrow-left" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(270, 128, 128)"></use></symbol>
<symbol id="arrow-bottom" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(180, 128, 128)"></use></symbol>
<symbol id="arrow-left" viewBox="0 0 256 256"><use href="#arrow-top" transform="rotate(270, 128, 128)"></use></symbol>
...

 いずれにせよ、スプライト化によりファイルサイズ短縮できた。全パターンをd属性値でパスデータ設定したら、もっと大きなサイズになっていただろう。

 めでたし めでたし。