SVGには、基本的な図形を描くタグがあらかじめ用意されている。直線も曲線も描ける<path>タグは、それらの中で最も自由度が高い。そのぶん使い方は複雑だが、要点を押えればあらゆる図形を<path>タグだけで表現できる。

さぞ多彩な属性値があるかと思いきや、同タグ特有の属性値は以下の2つだけである(もちろんfillstrokeなどの一般的なSVG属性も指定できる)。

属性名 概要
d "Path Data"の略。パスの形を定義するための文字列。
pathLength パスの長さを定義するための数値。デフォルトはnoneで、パスの長さがそのまま描画される。

このうち、曲者はd属性である。実用的な図形を描くためには、呪文のようなアルファベットと数字の羅列を長々と指定する必要がある。この文字列は、線の種類を示すコマンドとコマンドに渡されるパラメータの連なりである。

コマンドの書式

W3Cは、d属性に指定できる値について、以下のように定めている。

  • すべてのコマンドは1文字で表現される
  • 区切り文字には空白あるいはを使う
  • 余分な空白やカンマなどの区切り記号は省くことができる
  • 同じコマンドが連続する場合、最初の1回以外は省略できる
  • すべてのコマンドで絶対座標と相対座標を利用できる(前者は大文字、後者は小文字)
  • 読みやすさ向上のために、d属性には改行を含めることができる

参考:path dataに関する一般情報

ちなみにSVGの座標系では、左上の角が原点 ( 0 , 0 ) (0,0) となる。 X X 軸は右向きに増加し、 Y Y 軸は下向きに増加する。

<path>タグのコマンドは、コマンドの種類を示すアルファベット1文字と、座標を示す単位のない数値によって構成される。区切り文字は2種類使え、いずれも同じ意味を持ち、かつ省略できる。

そのため、書き手(あるいはデザインツールの作り手)の好みよって幾つかのバリエーションがある。

html
<svg width="200" height="100" viewBox="0 0 200 100" fill="none" stroke-width="3">
  <!--空白で区切る形-->
  <path d="M 1.5 1.5 L 100 1.5 50 86.8 Z" stroke="blue"/>
  <!--コマンドの前後を空白、座標をカンマで区切る形-->
  <path d="M 50,0 L 0,86.8 100,86.8 Z" stroke="red"/>
  <!--コマンドの前後の半角を省略する形-->
  <path d="M100 1.5L198.5 1.5 150 86.8Z" stroke="steelblue"/>
  <path d="M150 0L100 86.8 200 86.8Z" stroke="tomato"/>
<svg>

ご覧の通り、どの方法を用いてもパッと見で図形の全容を把握するのは激ムズである。

ただ、コーディングをしていると<path>を読み解かなければならないことが稀によくある。大枠だけでも掴んでおけば、のちのち役に立つ。そう信じて前に進む。

W3CやMDNのページでは、空白スペースで区切る形が使われている。これはコマンドが長くなると分かりづらいため、本稿では x x 座標と y y 座標の間にカンマ、他は空白スペースで区切る形を採用する。

なお、d属性の詳細な文法については、先に参照したページの「8.3.9 The grammar for path data」の項にバッカス・ナウア記法で記されている。

コマンドの種類

d属性で使うコマンドの種類は、直線にかんするコマンドと曲線にかんするコマンドに大別される。

直線にかんするコマンド

直線にかんするコマンドの種類は、さらに以下の4つに大別される。大文字と小文字の違いは、パラメータに渡す座標の原点がviewBoxの原点(左肩)か、直前の終点か、というだけである。

Mm

Mは、d属性に必ず指定する必要のあるコマンドである。個人的には、原点をデフォルトにしてもええんじゃないかと思うが、なぜか省略すると線が引かれない。

また、M属性だけを指定してもやはり線は描画されない。LなりHなりVなり、線を描画するコマンドとセットで描画する必要がある。

コマンド名 概要
M "Move To"の略。線の開始位置を与えられた絶対座標に移動する。
m 直前の点を基準に、線の開始位置を与えられた相対座標に移動する。

Ll

コマンド名 概要
L "Line To"の略。直前の点から、与えられた絶対座標に線を引く。
l 直前の点から、与えられた相対座標に線を引く。

以下のサンプルコードに含まれる2つの<path>タグでは、まずM 50 50で始点を矩形の中央に移動し、その後にLコマンドとlコマンドにそれぞれ同じ座標を指定して両者の違いを示す。Lが青線でlが赤線である。

html
<svg width="100px" height="100px" viewBox="0 0 100 100" style="border:solid 1px #ddd;" stroke-width="5px">
  <!--Lコマンドは、直前の点から指定された絶対座標への直線を引く。-->
  <path d="M 50,50 L 50,25" stroke="blue"></path>
  <!--lコマンドは、直前の点から指定された相対座標(差分の座標)への直線を引く。-->
  <path d="M 50,50 l 50,25" stroke="red"></path>
</svg>

Lコマンドでは、始点である(50,50)から(50,25)に向かって直線が引かれている。一方のlコマンドでは、始点の(50,50)に(50,25)が差分として足され、(100,75)に向かって直線が引かれていることがわかる。

HhVv

座標を連続して書くと、直前の点から垂直、あるいは水平の線を引く場合に記述が冗長になる。これを簡略化して書けるのがH(h)コマンドとV(v)コマンドである。

コマンド名 概要
H "Horizontal Line To"の略。直前の点から、与えられたx軸の絶対座標に向けて線を引く。
h 直前の点から、与えられたx軸の相対座標に向けて線を引く。
V "Vertical Line To"の略。直前の点から、与えられたy軸の絶対座標に向けて線を引く。
h 直前の点から、与えられたy軸の相対座標に向けて線を引く。

先のサンプルと同様に、2つのを用意し、それぞれ矩形の中心に移動してからHコマンドとhコマンドを指定する。

html
<svg width="100" height="100" viewBox="0 0 100 100" style="border:solid 1px #ddd;" stroke-width="5px">
  <!-- 直前の点から、(25,50)の座標に向けて水平の線を引く -->
  <path d="M 50,50 H 25" stroke="blue"></path>
  <!-- 直前の点から、x軸上を+25移動した座標に向けて水平の線を引く-->
  <path d="M 50,50 h 25" stroke="red"></path>
</svg>

棒磁石みたいになってしまった。

Vvも、ほとんど同じ使い心地である。

html
<svg width="100px" height="100px" viewBox="0 0 100 100" style="border:solid 1px #ddd;" stroke-width="5px" >
  <!-- 直前の終点から、(50,25)の座標に向けて垂直の線を引く -->
  <path d="M 50,50 L 50,50 V 25 " stroke="blue"></path>
  <!-- 直前の終点から、y軸上を+25移動した座標に向けて垂直の線を引く-->
  <path d="M 50,50 L 50,50 v 25" stroke="red"></path>
</svg>

Z

Zも、Lコマンドを簡略に書くためのコマンドである。座標を指定する必要はなく、Zの一文字で終点から始点を結ぶ線を引いてくれる。

コマンド名 概要
Z "Close Path"の略。直前の点と線の始点を結ぶ線を引く。
z 大文字と小文字に違いはない。

ちなみにZコマンドを使った後でも、コマンドは続けることができる。

html
<svg width="100px" height="100px" viewBox="0 0 100 100" style="border:solid 1px #ddd;" fill="none" stroke-width="5px">
  <path d="M 5,5 L 5,5 5,95 95,95 95,5 Z" stroke="blue"></path>
  <path d="M 20,20 L 20,20 20,80 80,80 80,20 z" stroke="red"></path>
  <path d="M 35,35 L 35,35 35,65 65,65 65,35 Z M 65,35 L 35,65" stroke="purple"></path>
</svg>

曲線にかんするコマンド

曲線にかんするコマンドは、ベジエ曲線を用いるものと円弧を用いるものに大別される。

ベジエ曲線とは、計算によって描く、任意の2点を結ぶ滑らかな曲線である。この計算方法は、1960年代にピエール・ベジエというフランスのエンジニアが実用化した。

ベジエ曲線では、制御点という点を動かして線のカーブをコントロールする。制御点は、線を引っ張る重力を持った不思議な点、とでも考えれば良い(がっちり理解したければ、線形補完を勉強すると良い)。

制御点(始点と終点も含まれる)の個数が n + 1 n+1 のとき、そのベジエ曲線は、「 n n 次のベジエ曲線」などと表現される。

SVGのd属性では、これらのうち2次ベジエ曲線と3次ベジエ曲線をサポートしている。あと円弧。各コマンドは以下の通りである。

QqTt

2次ベジエ曲線は、3つの制御点によって描かれる。

まず始点と制御点を結ぶ直線上、そして制御点と終点を結ぶ直線上で、それぞれ同じ割合で点を移動する。次に、これらの2つの点を直線で結ぶ。

この直線上に点を置き、先の2つの点と同じ割合で移動させると、その移動する点の軌跡が求める2次ベジエ曲線となる。

例えば始点、制御点、終点を等距離に、正三角形を描くように配置すると、三角形の高さの半分に曲線の頂点が来る。この事実をうっすらイメージしておくと、以下のコマンドの働きがわかりよいはずである。

コマンド名 概要
Q "Quadratic Bézier curve"の略。絶対座標で制御点と終点を受け取り、終点への曲線を引く。
q 相対座標で制御点と終点を受け取り、始点から終点への曲線を引く。
T Smoothの略(たぶん)。1つ前の制御点を、直前の点を中心にした点対称の位置に置き、雑多い座標で渡された終点に向けて曲線を引く。
t Tコマンドの相対座標版。

まずQコマンドとqコマンドの働きを見る。

以下は、それぞれのコマンドを2つ続け、2つのカーブを持つ曲線を描いている。わかりやすいように、始点と終点を含む各制御点の x 座標 x座標 は等間隔(20刻み)に設定し、奇数個目の y y 座標を50に、偶数個目の y y 座標は上端と下端に(qコマンドは先に下端、後に上端)それぞれ設定している。

html
<svg width="100" height="100" viewBox="0 0 100 100" style="border:solid 1px #ddd;">
  <path d="M 10,50 Q 30,0 50,50 70,100 90,50" stroke="blue" stroke-width="3" fill="none"/>
  <path d="M 10,50 q 20,50 40,0 20,-50 40,0" stroke="red" stroke-width="3" fill="none"/>
</svg>

Qコマンドもqコマンドも、制御点を結ぶ三角形の高さの半分( 25 25 あるいは 75 75 の位置)に頂点が来る曲線が描かれていることがわかる。

続いてTコマンドとtコマンドの働きを見る。先のQコマンドとqコマンドのサンプルコードは、それぞれの制御点を点対称に配置している。これをそのままTコマンドとtコマンドに置き換える。

html
<svg width="100" height="100" viewBox="0 0 100 100" style="border:solid 1px #ddd;">
  <path d="M 10,50 Q 30,0 50,50 T 90,50" stroke="blue" stroke-width="3" fill="none"/>
  <path d="M 10,50 q 20,50 40,0 t 40,0" stroke="red" stroke-width="3" fill="none"/>
</svg>

同じ図形が描かれていることがわかる。

SsCc

2次ベジエ曲線は、3つ制御点によって描かれる。3次ベジエ曲線は、これにもう1つ制御点が加わったものである。

コマンド名 概要
C "Cubic Bézier curve"の略。絶対座標で3つの制御点を受け取り、最後の制御点に向けて曲線を引く。
c 相対座標で3つの制御点を受け取り、最後の制御点に向けて曲線を引く。
S Smoothの略(たぶん)。1つ前の2つの制御点を、直前の点を中心にした点対称の位置に置き、渡された終点に向けて曲線を引く。
s Sコマンドの相対座標版。

まずはCコマンドとcコマンドのサンプルを示す。それぞれ、2つのコマンドを点対称に繋げている。また、各コマンドの制御点は、正四角形になるように配置している。

html
<svg width="100" height="100" viewBox="0 0 100 100" style="border:solid 1px #ddd;">
  <path d="M 10,50 C 10,10 50,10 50,50 C 50,90 90 90 90 50" stroke="blue" stroke-width="3" fill="none"/>
  <path d="M 10,50 c 0,40 40,40 40,0 c 0,-40 40,-40 40,0" stroke="red" stroke-width="3" fill="none"/>
</svg>

Sコマンドは、1つ前のコマンドの制御点(終点の1つ前)の点対称に制御点を置く。上のサンプルコードと下のサンプルコードを見比べると、後者では2つ目のコマンドの最初の制御点が省略されていることがわかる。

html
<svg width="100" height="100" viewBox="0 0 100 100" style="border:solid 1px #ddd;">
  <path d="M 10,50 C 10,10 50,10 50,50 S 90 90 90 50" stroke="blue" stroke-width="3" fill="none"/>
  <path d="M 10,50 c 0,40 40,40 40,0 s 40,-40 40,0" stroke="red" stroke-width="3" fill="none"/>
</svg>

Aa

円弧を扱うAコマンドは、ベジエ曲線よりも扱いが簡単に思える(そんな感じのことが<path>タグに関するMDNの解説ページにも書いてある)。

個人的な感覚では、Aコマンドの方がクセが強く、理解するのに難儀した。

コマンド名 概要
A "Elliptical Arc Curves"の略。後述するパラメータに基づいて、一意に定められた円弧を描画する。
a Aコマンドの相対座標版。

Aコマンドは、以下のパラメータを取る。

パラメータ名 概要
rx 楕円の水平方向の半径。
ry 楕円の垂直方向の半径。
x-axis-rotation 楕円の傾き。
large-arc-flag 円弧の大きい方か小さい方かを判定するフラグ。大きい方は1、小さい方は0
sweep-flag 円弧を始点から時計回りに描くか、反時計回りに描くか。時計回りは1、反時計回りは0
x 終点の x x 座標。
y 終点の y y 座標。

なお、指定された半径rxおよびryが始点と終点の間の距離に対して大きすぎるか小さすぎる場合、描画に使われる楕円の半径は自動的に調整される。

以下のサンプルでは、始点と終点の距離を短径、その倍の長さを長径に指定した楕円の円弧を使って、これらのパラメータの働きを確かめる。

html
<svg width="100" height="100" viewBox="0 0 100 100" style="border:solid 1px #ddd;">
  <path d="M 40,50 A 10,20 0 1 0 60,50" stroke="blue" stroke-width="3" fill="none"/>
  <path d="M 40,50 A 10,20 0 0 1 60,50" stroke="red" stroke-width="3" fill="none"/>
  <path d="M 40,50 A 10,20 0 1 1 60,50" stroke="steelblue" stroke-width="3" fill="none"/>
  <path d="M 40,50 A 10,20 0 0 0 60,50" stroke="salmon" stroke-width="3" fill="none"/>
</svg>

<svg width="100" height="100" viewBox="0 0 100 100" style="border:solid 1px #ddd;">
  <path d="M 40,50 A 10,20 60 1 0 60,50" stroke="blue" stroke-width="3" fill="none"/>
  <path d="M 40,50 A 10,20 60 0 1 60,50" stroke="red" stroke-width="3" fill="none"/>
  <path d="M 40,50 A 10,20 60 1 1 60,50" stroke="steelblue" stroke-width="3" fill="none"/>
  <path d="M 40,50 A 10,20 60 0 0 60,50" stroke="salmon" stroke-width="3" fill="none"/>
</svg>

左右2つとも、large-arc-flagsweep-flagの組み合わせ(4パターン)を網羅する形でパスを指定している。円弧を時計回りに描くか、反時計回りで描くかを指定するsweep-flagを同じ図形に対して両方指定しているため、完全な楕円が描かれている。

ただ、左側は2つの円弧(薄青とピンク)しか描かれていない。これは、1つ目のパスの上に3つ目のパスが、2つ目のパスの上に4つ目のパスが重なっているためである。

始点と終点によって、楕円は2つの円弧に分けられる。large-arc-flagは、このうちの大きい方を描くか小さい方を描くかを指定する。

しかし、例えば楕円に角度がついていなかったり、楕円が真円になっていたりすると、始点と終点で2つに分けた円弧の大きさが変わらない。こういった状況でlarge-arc-flagを指定しても、図形は変わらない。

右は、左側のパスをコピーし、x-axis-rotationに60を指定した(60度傾けた)ものである。こちらは、large-arc-flagの働きがよくわかる。

x-axis-rotationは、楕円の x x 軸を傾け、その上で始点と終点が楕円の線上に来るように調整する。単純に傾けるわけではないため、注意が必要である。

参考資料