SVGでシンプルな円形プログレスバーを実装する

本記事では、svgの基本を抑えつつ、**円形プログレスバー**をSVGとCSSでシンプルに実装する方法を解説します。

こんなのが作れるようになります👇
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564152/83d75da5-2705-4ed8-b0a5-cf9a885d453d.png)

## ✅ svgで円を表示する方法
svgの`circle`タグを使用することで、簡単に円を表示することができます。
```html
<svg viewBox="0 0 100 100" width="100" height="100" fill="none">
    <circle cx="50" cy="50" r="45" stroke="lightgray" stroke-width="10" />
</svg>
```

各属性について説明します。
| 属性 | 説明 |
|---|---|
| viewBox | SVGの描画領域を定義します。`xの最小値`、`yの最小値`、`幅`、`高さ`の順で指定します。 |
| width・height| SVGの表示サイズ（px単位）を指定します。 |
| cx・cy | 円の中心座標を指定します。デフォルトでは中心が座標の `(0, 0)` に配置されます。|
| r | 円の半径を指定します。`stroke-width` の影響を受けるので、viewBoxに収めるには注意が必要です。|
| stroke | 円周の線の色を指定します。|
| stroke-width | 円周の線の太さを指定します。線の中心を軸に、内側と外側の両方に太さが広がります。|


横幅：100px、高さ：100pxの円に、10pxのstrokeを指定した場合、
半径`r`の値は45pxになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564152/0c73220d-d74a-40d7-b3c8-7dda39c9d232.png" width="50%">

`viewBox="0 0 100 100"`の中央に円を表示するには、
`cx="50"` `cy="50"`と指定する必要があります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564152/4b3924be-311c-4bf4-931f-6e5593d2be2a.png" width="50%">

## ✅ 円形プログレスバーの仕組み
今回作成するプログレスバーは、
- 進捗の背景となる円
- 進捗を表す円

を重ねることで実現できます。

進捗を表す円は、12時の位置から線が時計回りに描画される場合
進捗率25%の時は、最初の4分の1だけ色がついた線で描画されていることになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564152/8ece04fe-d7c1-406e-83ed-d02905b4a138.png" width="80%">

このように円周の一部だけに色をつけるには、
- `stroke-dasharray`
- `stroke-dashoffset`

を使って、円周を破線で表すことで実現できます。

|属性|説明|
|---|---|
|stroke-dasharray|線の長さと空白の長さを指定し、破線のパターンを定義します。|
|stroke-dashoffset|破線をどの位置から見せるかを指定します。|

### 💡値の求め方
半径45px、進捗率25%の場合を例に求め方を説明します。

#### ①円周の長さを求める
- 半径が45pxなので、直径は90px
- 円周率を3.14とすると、円周の長さは 90 × 3.14 = 282.6pxとなります

> 円周の長さ = 直径 × 円周率（3.14）

#### ②`stroke-dasharray`に指定する
求めた円周の長さを、次のように`stroke-dasharray`に指定します。
```
stroke-dasharray="282.6 282.6"
```
これは、「282.6pxの実線」と「282.6pxの空白」が1セットになった破線を定義するという意味です。
※破線は実線から始まります。

#### ③`stroke-dashoffset`を求める
進捗率25%を表現するには、円の最初の1/4だけ実線を表示し、それ以外は空白にする必要があります。
そのためには、実線部分の開始位置を、75%分ずらして見せる必要があります。

> 計算式：282.6 ÷ 4 × 3 = 211.95px

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564152/29e25af7-cb67-4f37-a331-1e6110fb1dfc.png)

これを`stroke-dashoffset`に指定します。

```
stroke-dashoffset="211.95"
```

#### ⚠️注意点：circleタグの描画開始位置は3時の方向
SVGの`<circle>`は、デフォルトでは 3時の方向（右側）から描画が始まります。
進捗の開始を 12時方向（上） にしたい場合は、CSSで円を -90度回転させる必要があります。

それらを考慮すると、ソースコードは以下のようになります。
```html:html
<svg viewBox="0 0 100 100" width="100" height="100" fill="none">
    <!-- 背景となる円 -->
    <circle cx="50" cy="50" r="45" stroke="lightgray" stroke-width="10" />
    <!-- 進捗を表す円 -->
    <circle cx="50" cy="50" r="45" stroke="#FB7E51" stroke-width="10" stroke-dasharray="282.6 282.6" stroke-dashoffset="211.95" class="progressCircle" />
</svg>

<style>
.progressCircle {
    position: relative;
    transform-origin: 50% 50%;
    transform: rotate(-90deg);
}
</style>
```

**表示結果**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564152/2ac12c8a-cae6-4db5-801a-c56d69b352b3.png)

このように、stroke-dasharrayで破線を定義し、その破線をstroke-dashoffsetでどの位置から見せるかを指定することで、進捗率を表現することができます。

その他の進捗率は`stroke-dashoffset`の値を変更することによって、任意の進捗率を表現することができます。

##### 進捗率50%の場合
進捗率が50%の場合は、円の半分だけ実線で表示されるようにします。
円周の50%分を`stroke-dashoffset`に指定します。

> 計算式：282.6 × 0.5 = 141.3px

##### 進捗率75%の場合
進捗率が70%の場合は、円の4分の3だけ実線で表示されるようにします。
円周の25%分を`stroke-dashoffset`に指定します。

> 計算式：282.6 × 0.25 = 84.78px

```html:html
<!-- 進捗50% -->
<svg viewBox="0 0 100 100" width="100" height="100" fill="none">
    <circle cx="50" cy="50" r="45" stroke="lightgray" stroke-width="10" />
    <circle cx="50" cy="50" r="45" stroke="#FFE369" stroke-width="10" stroke-dasharray="282.6 282.6" stroke-dashoffset="141.3" class="progressCircle" />
</svg>

<!-- 進捗75% -->
<svg viewBox="0 0 100 100" width="100" height="100" fill="none">
    <circle cx="50" cy="50" r="45" stroke="lightgray" stroke-width="10" />
    <circle cx="50" cy="50" r="45" stroke="#7AEAD6" stroke-width="10" stroke-dasharray="282.6 282.6" stroke-dashoffset="70.65" class="progressCircle" />
</svg>

<style>
.progressCircle {
    position: relative;
    transform-origin: 50% 50%;
    transform: rotate(-90deg);
}
</style>
```

**表示結果**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3564152/f7899ce2-aacb-4158-b7f1-fbcfb090aba0.png)

## まとめ
本記事では、SVGの基本構造を理解しながら、SVGとCSSで円形プログレスバーを表現する方法を解説しました。

ポイントを以下にまとめます：
### `<circle>`で円を重ねる
- 進捗の背景となる円
- 進捗を表す円

> 背景が不要な場合、1つの円でも表現できます

### 進捗は`stroke-dasharray`と`stroke-dashoffset`で表現する
- 円周の長さを`stroke-dasharray`に指定することで、円周の長さ分の実線と空白が繰り返される破線を定義する
- `stroke-dashoffset`にどの位置から破線を見せるのか指定することで、任意の進捗率を表現できる

### `<ciecle>`はデフォルトで3時の位置から描画される点に注意
- 12時の位置から描画されるようにするには、`<svg>`や`<circle>`を-90度回転させ表示させる必要がある