SVGのパスについて詳しく調べてみる（SVG.js）

SVGのパス関係の知識が必要になってきたので仕様を確認していきます。本記事では直接HTMLなどでは書かずにSVG.jsを使っていきます（ただしパスの仕様自体はHTMLでやってもSVG.jsを使っても変わりません）。

https://github.com/svgdotjs/svg.js

以下のMDNやsvg.jsのドキュメントを見ていきます。

https://developer.mozilla.org/ja/docs/Web/SVG/Tutorial/Paths

https://svgjs.dev/docs/3.0/shape-elements/#svg-path

# 使うもの

- SVG.js v3.1.1
- JSFiddle （例 : https://jsfiddle.net/yar6x4hk/202/ ）

# パスの描画の基本

表示の確認のためにまずはパスの確認用の描画のインスタンスの生成処理について触れていきます。パスを描画するためにはpathメソッドを使います。引数に各パス設定の文字列を指定します。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('M 50 100 Q 100 20 150 100 T 250 100');
path.attr({stroke: '#333', 'stroke-width': 5, fill: 'transparent'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/9fd2462e-6193-5acc-039a-e1ebc5bc242d.png)

# パスの位置を移動させるM

パスの位置を移動させるには`M <X座標> <Y座標>`といったように指定します。例えばX=50, Y=100の位置に移動させたければ`M 50 100`といったように書きます。Mはmove toのMのようです。

パスの位置が変更になるだけでこの記述だけでは表示上には影響しません（これだけだと何も表示されません）。

# 直線を描画するL

指定位置に向かって直線を描画したい場合には`L <X座標> <Y座標>`といったように指定します。例えばX=50, y=50の位置からX=150, Y=50の位置に直線を描画したい場合には`M 50 50 L 100 50`といったように書きます。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('M 50 50 L 150 50');
path.attr({stroke: '#333', 'stroke-width': 5, fill: '#fff'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/fdd793af-c995-a923-2653-11526bcba9ef.png)

# パスの指定はどんどん繋げていくことができる

パスの指定はどんどん繋げていくことができます。例えば以下のように2つ目のLを指定すれば折れ線となります。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('M 50 50 L 150 50 L 150 150');
path.attr({stroke: '#333', 'stroke-width': 5, fill: '#fff'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/8aa907e0-f032-170b-47cf-5becc7d41fd7.png)

Mを複数入れて複数の線を描画する・・・といったこともできます。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('M 50 50 L 150 50 M 50 150 L 150 150');
path.attr({stroke: '#333', 'stroke-width': 5, fill: '#fff'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/c0aeedd7-f007-1ba3-71d6-05b8e2ba5143.png)

# 水平方向に対する直線を描画するH

`H <X座標>`とすることで水平方向に対する直線を描画することができます。Lと似たような感じとなりますがY座標の指定が要らなくなるため水平方向の直線を描きたい場合にはこちらの方がシンプルです。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('m 50 50 H 150');
path.attr({stroke: '#333', 'stroke-width': 5, fill: '#fff'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/b58b699e-7564-580b-833f-5159ae0737f5.png)

# 垂直方向に対する直線を描画するV

`V <Y座標>`とすると垂直方向に対する直線を描画することができます。Hと対になる設定となります。こちらも水平方向の座標を変えない場合にはLよりも記述がシンプルになります。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('m 50 50 V 150');
path.attr({stroke: '#333', 'stroke-width': 5, fill: '#fff'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/97e8b4df-393f-cf31-2444-d18bc1efc396.png)

# 終点と始点を繋げるZ

パスの最後にZと終点と始点のパスを繋げることができます。Lなどで始点の座標を指定するのと同じ挙動になりますがこちらの方がシンプルですし始点の座標を変えた際などにも楽です。

以下の例ではX=150, Y=150の位置からZの記述によってX=50, Y=50の始点に線が繋がれています。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('m 50 50 H 150 V 150 Z');
path.attr({stroke: '#333', 'stroke-width': 5, fill: '#fff'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/c326211e-e803-9e0f-eb32-284f75985422.png)


# パスの塗りを透明にする設定

デフォルトだとパスの内部は塗り設定がされます（フォトショやイラレなどでお馴染みな感じの挙動になります）。以下のようにfill設定をしていないと黒く塗られます。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('m 50 50 H 150 V 150 Z');
path.attr({stroke: '#333', 'stroke-width': 5});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/30f76c56-b057-4f58-6b63-e0481cd0ead1.png)

fillにnullや空文字などを指定しても塗りが消えず、透明度設定をすると線も消える・・・感じだったためここまでのコードサンプルでは線のみが見えやすくなるように背景色と同じ白色を塗りに設定していましたが、MDNの資料を読んでいたらどうやらtransparentとfillに設定することで塗りだけ透明にして線のみを表示することができるようです。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('m 50 50 H 150 V 150 Z');
path.attr({stroke: '#333', 'stroke-width': 5, fill: 'transparent'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/37131a09-f480-5574-a28d-05c4a1d7dc97.png)

# 小文字による想定値指定

LやH、Vなどの記号は小文字にすると座標の指定が相対値による指定となります（前節までで触れてきた大文字による指定は絶対値による座標の指定です）。

例えば`h 50`とすれば今の座標から50加えたX座標に線を引く形となり、`h -50`といったように負の座標を指定すれば今の座標から50左側に移動したX座標の位置に線を引く形となります。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('M 50 50 h 50 v 50 h -50');
path.attr({stroke: '#333', 'stroke-width': 5, fill: 'transparent'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/bbf638f8-ad9e-e2ac-20d2-32b35a3c6063.png)

# 3次ベジェ曲線の指定

フォトショやイラレなどのAdobeソフト、もしくは3Dなどのソフトを使われている方にはお馴染みのベジェ曲線について触れていきます。制御点の指定を2つ必要とする3次ベジェ曲線と1つのみ必要とする2次ベジェ曲線の2つがありますが、まずは3次ベジェ曲線について触れていきます。

記法としては`C <1つ目の制御点のX> <1つ目の制御点のY> <2つ目の制御点のX> <2つ目の制御点のY> <曲線の末端のX> <曲線の末端のY>`といった具合にXとY座標がそれぞれ3つずつ必要になります。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('M 50 100 C 50 50 100 50 100 100');
path.attr({stroke: '#333', 'stroke-width': 5, fill: 'transparent'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/57ec46de-d621-656e-5413-50981c30853a.png)

分かりやすいように各座標に円を追加してみます。先頭のXとYの指定（1つ目の制御点）がシアン、真ん中のXとYの指定（2つ目の制御点）がマゼンタ、3つ目のXとYの指定（曲線の末端）を緑に設定しています。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('M 50 100 C 50 50 100 50 100 100');
path.attr({stroke: '#333', 'stroke-width': 5, fill: 'transparent'});

var circle1 = draw.circle(10);
circle1.x(50 - 5);
circle1.y(50 - 5);
circle1.fill('#0af');

var circle2 = draw.circle(10);
circle2.x(100 - 5);
circle2.y(50 - 5);
circle2.fill('#f0a');

var circle3 = draw.circle(10);
circle3.x(100 - 5);
circle3.y(100 - 5);
circle3.fill('#0fa');
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/bfbdb506-ba3a-ce1a-3142-1e3da1b8723d.png)

他と同様に小文字のcを使うことで相対座標で指定することができます。注意点として基準点は全て始点となります。2つ目の制御点は曲線の末端を基準にしたりはしないようです。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('M 50 100 c 0 -50 50 -50 50 0');
path.attr({stroke: '#333', 'stroke-width': 5, fill: 'transparent'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/aab26c3d-a57e-eada-92a1-34527740318c.png)

# 3次ベジェ曲線の曲線を滑らかに繋ぐS

滑らかに曲線を繋ぎたい場合はSを使います。この場合1つ目の制御座標は前の曲線のものの対向（反転させた位置）のものが使われるため指定が不要になります（対向の座標の制御点が使われるためスムーズな曲線を表現できます）。つまり制御点のXとYの指定が1つのみとなります（イラレのペンツールなどでもベジェ曲線で片方を伸ばしたら対向の制御点も同じ感じで伸びたりしますがそれに近い感じの挙動になります）。`S <制御点X> <制御点Y> <曲線の末端のX> <曲線の末端のY>`といった形で指定します。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('M 50 100 C 50 50 100 50 100 100 S 150 150 150 100');
path.attr({stroke: '#333', 'stroke-width': 5, fill: 'transparent'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/b6c757ed-b82e-37f0-454b-41ba9b0f0d67.png)

以下のコードではSによる制御点の指定箇所をシアン、曲線の末端の座標の指定箇所をマゼンタの円で表示しています。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('M 50 100 C 50 50 100 50 100 100 S 150 150 150 100');
path.attr({stroke: '#333', 'stroke-width': 5, fill: 'transparent'});

var circle1 = draw.circle(10);
circle1.x(150 - 5);
circle1.y(150 - 5);
circle1.fill('#0af');

var circle2 = draw.circle(10);
circle2.x(150 - 5);
circle2.y(100 - 5);
circle2.fill('#f0a');
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/3bde5e9c-0d91-4c7a-ea8b-769a3aca5f95.png)

他と同様に小文字のsを使うことで始点の座標からの相対座標で指定していくことも可能です。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('M 50 100 C 50 50 100 50 100 100 s 50 50 50 0');
path.attr({stroke: '#333', 'stroke-width': 5, fill: 'transparent'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/5d398b60-5617-ab24-5302-a65fc9d9f01f.png)

# 2次ベジェ曲線の指定

2次ベジェ曲線では制御点は1つのみ使います。細かい制御が効きづらくなる一方で記述がシンプルになるため、2次ベジェ曲線で事足りる場合などに便利です。

記法としては`Q <制御点のX> <制御点のY> <曲線の末端のX> <曲線の末端のY>`といったように指定します。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('M 50 100 Q 100 20 150 100');
path.attr({stroke: '#333', 'stroke-width': 5, fill: 'transparent'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/bae0004b-71f1-1fb6-b3f0-a2e36305851d.png)

以下の記述では設定している制御点をシアン、曲線の末端の座標をマゼンタの円で表示しています。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('M 50 100 Q 100 20 150 100');
path.attr({stroke: '#333', 'stroke-width': 5, fill: 'transparent'});

var circle1 = draw.circle(10);
circle1.x(100 - 5);
circle1.y(20 - 5);
circle1.fill('#0af');

var circle2 = draw.circle(10);
circle2.x(150 - 5);
circle2.y(100 - 5);
circle2.fill('#f0a');
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/ec5314b0-8f93-1f33-e35a-8a998cf9bd9a.png)

他と同様にqの小文字で始点からの相対値で座標を指定することができます。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('M 50 100 q 50 -80 100 0');
path.attr({stroke: '#333', 'stroke-width': 5, fill: 'transparent'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/c441993d-8504-ddb4-67a5-ce3a50822273.png)

# 2次ベジェ曲線の曲線を滑らかに繋ぐT

3次ベジェ曲線と同様に2次ベジェ曲線でも曲線を滑らかに繋ぐためのTの指定が存在します。こちらは制御点の指定は無くなります（直前の制御点の対向の位置が使われる形になります）。`T <曲線の末端のX> <曲線の末端のY>`といったように書きます。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('M 50 100 Q 100 20 150 100 T 250 100');
path.attr({stroke: '#333', 'stroke-width': 5, fill: 'transparent'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/9fd2462e-6193-5acc-039a-e1ebc5bc242d.png)

他と同様にtの小文字で始点からの相対値で末端の座標を指定することができます。

```js
var draw = SVG().addTo('body').size(600, 600);

var path = draw.path('M 50 100 Q 100 20 150 100 t 100 0');
path.attr({stroke: '#333', 'stroke-width': 5, fill: 'transparent'});
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/b645a46a-1a45-16dc-24a5-101e80b995a9.png)

# 曲線に円弧を使うAもあるものの・・・

他にも円弧を使う形のAの指定もあります。が、軽く触って見た感じ結構設定が直観的とは言い難く個人的に使う機会が少なそう・・・という所感を受けたため今回は触れずに終わります。将来必要になってきたらまた深堀りしようと思います。

# 参考文献・参考サイトまとめ

https://developer.mozilla.org/ja/docs/Web/SVG/Tutorial/Paths

https://svgjs.dev/docs/3.0/shape-elements/#svg-path