【React】SVGのPath上を移動する軌道アニメーション

<!-- =============================================== -->
# 概要
<!-- =============================================== -->
SVGのpath上を移動するアニメーションについて、実装例をふまえて調べたことをまとめました。

![output(video-cutter-js.com) (4).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/e4fbe6cf-0a85-f4af-bfde-2ab0254189a7.gif)


<!-- =============================================== -->
# 実装
<!-- =============================================== -->
### べた書き + 説明

```.tsx
import React, { VFC } from 'react';
import { css } from '@emotion/css';

export const SVGPathAnimation: VFC = () => {
	return (
		<div className={styles.container}>
			<svg viewBox="0 0 193 193" fill="none" xmlns="http://www.w3.org/2000/svg">
				<g id="react-icon" transform="translate(6.5 0)">
					<path
						id="circle"
						d="M90 80.725C98.8769 80.725 106.073 87.9074 106.073 96.7671C106.073 105.627 98.8769 112.809 90 112.809C81.123 112.809 73.9266 105.627 73.9266 96.7671C73.9266 87.9074 81.123 80.725 90 80.725Z"
						fill="#00D8FF"
					/>
					<path
						id="ring1"
						d="M139.831 69.9227C141.693 70.4192 143.541 70.9592 145.373 71.542C163.966 77.4769 175.982 86.7733 175.982 96.3865C175.982 106.411 163.141 116.213 143.405 122.276C142.363 122.597 141.293 122.905 140.204 123.204L133.09 124.933C125.959 126.461 118.097 127.581 109.774 128.266L100.296 128.847C96.8819 128.988 93.4132 129.061 89.9041 129.061C86.4886 129.061 83.1282 129 79.8317 128.88L70.2911 128.353C61.949 127.725 54.1434 126.685 47.0917 125.239L39.9607 123.568C38.3974 123.154 36.8455 122.704 35.3064 122.218C16.3222 116.202 3.82609 106.279 3.82609 96.3865C3.82609 86.8082 15.4661 77.5742 33.6932 71.6817C35.7301 71.0232 37.8585 70.4047 40.0643 69.8235L47.1933 68.1443C54.3189 66.6472 62.0693 65.5298 70.166 64.8339L79.7705 64.2154C86.5492 63.921 93.3392 63.9223 100.118 64.2191L109.692 64.8426C117.812 65.5491 125.568 66.6858 132.711 68.2148L139.831 69.9227Z"
						stroke="#00D8FF"
						strokeWidth="1"
					/>
					<path
						id="ring2"
						d="M91.8267 39.9871C93.1875 38.6233 94.5789 37.2932 95.9997 35.9979C110.436 22.8629 124.495 17.105 132.82 21.9116C141.502 26.924 143.57 42.9457 138.953 63.069C138.709 64.132 138.441 65.212 138.156 66.3051L136.096 73.3308C133.855 80.27 130.893 87.6392 127.325 95.1892L123.089 103.688C121.504 106.715 119.833 109.756 118.078 112.795C116.37 115.753 114.637 118.632 112.885 121.427L107.658 129.426C102.944 136.337 98.1405 142.576 93.3618 147.96L88.3496 153.3C87.2094 154.447 86.0436 155.566 84.8534 156.656C70.1508 170.089 55.3096 175.949 46.7424 171.003C38.4474 166.214 36.2705 151.516 40.2809 132.785C40.7292 130.692 41.2577 128.539 41.8573 126.338L43.9675 119.325C46.2338 112.405 49.1413 105.135 52.587 97.7748L56.8536 89.1478C59.988 83.13 63.3841 77.2504 67.0305 71.5283L72.3576 63.5486C77.0295 56.8695 81.8918 50.7212 86.7875 45.2995L91.8267 39.9871Z"
						stroke="#00D8FF"
						strokeWidth="1"
					/>
					<path
						id="ring3"
						d="M41.8994 66.5927C41.3987 64.7323 40.9425 62.8622 40.5312 60.9841C36.3742 41.9142 38.4171 26.8599 46.7424 22.0533C55.4241 17.0409 70.3335 23.2606 85.452 37.3211C86.2508 38.0636 87.0522 38.8356 87.8561 39.6294L92.9106 44.9259C97.7994 50.3369 102.701 56.5862 107.455 63.4515L112.697 71.3691C114.526 74.2555 116.324 77.223 118.078 80.2619C119.786 83.2198 121.413 86.1606 122.958 89.0753L127.271 97.6014C130.899 105.14 133.901 112.419 136.174 119.25L138.293 126.261C138.716 127.821 139.102 129.39 139.451 130.966C143.733 150.415 141.388 166.199 132.82 171.145C124.525 175.934 110.708 170.47 96.4919 157.632C94.9032 156.197 93.3033 154.663 91.6971 153.043L86.6784 147.709C81.819 142.286 76.9761 136.133 72.3251 129.469L66.9872 121.461C63.3429 115.737 59.949 109.856 56.8168 103.837L52.5697 95.2342C49.1214 87.8486 46.228 80.5636 43.9805 73.613L41.8994 66.5927Z"
						stroke="#00D8FF"
						strokeWidth="1"
					/>

					{/* ring1 軌道を移動するCircle */}
					<circle r="5" fill="#00D8FF">
						<animateMotion dur="5s" repeatCount="indefinite">
							<mpath xlinkHref="#ring1" />
						</animateMotion>
					</circle>

					{/* ring2 軌道を移動するCircle */}
					<circle r="5" fill="#00D8FF">
						<animateMotion
							dur="7s"
							repeatCount="indefinite"
							keyPoints="1;0"
							keyTimes="0;1"
							calcMode="linear">
							<mpath xlinkHref="#ring2" />
						</animateMotion>
					</circle>

					{/* ring3 軌道を移動するCircle */}
					<circle r="5" fill="#00D8FF">
						<animateMotion
							dur="10s"
							repeatCount="indefinite"
							keyPoints="1;0"
							keyTimes="0;1"
							calcMode="linear">
							<mpath xlinkHref="#ring3" />
						</animateMotion>
					</circle>
				</g>
			</svg>
		</div>
	)
}
```

* path上を移動するアニメーションを実装するためには、**animateMotion**タグを使用します。

https://developer.mozilla.org/ja/docs/Web/SVG/Element/animateMotion

* pathタグで軌道を定義している場合は、**mpath**タグを使用することでpathを参照することができます。

https://developer.mozilla.org/ja/docs/Web/SVG/Element/mpath

* 逆方向に移動させる場合は、`animateMotion`タグに以下のプロパティを追加します。

`keyPoints="1;0"`
`keyTimes="0;1"`
`calcMode="linear"`

https://stackoverflow.com/questions/32423332/how-to-reverse-the-moving-direction-of-the-svg-animation-on-the-mpath

### コンポーネント化
もう少し綺麗に書くと、

```.tsx
import React, { VFC } from 'react';
import { css } from '@emotion/css';

export const SVGPathAnimation: VFC = () => {
	return (
		<div className={styles.container}>
			<svg viewBox="0 0 193 193" fill="none" xmlns="http://www.w3.org/2000/svg">
				<g id="react-icon" transform="translate(6.5 0)">
					<path
						id="circle"
						d="M90 80.725C98.8769 80.725 106.073 87.9074 106.073 96.7671C106.073 105.627 98.8769 112.809 90 112.809C81.123 112.809 73.9266 105.627 73.9266 96.7671C73.9266 87.9074 81.123 80.725 90 80.725Z"
						fill="#00D8FF"
					/>
					<path
						id="ring1"
						d="M139.831 69.9227C141.693 70.4192 143.541 70.9592 145.373 71.542C163.966 77.4769 175.982 86.7733 175.982 96.3865C175.982 106.411 163.141 116.213 143.405 122.276C142.363 122.597 141.293 122.905 140.204 123.204L133.09 124.933C125.959 126.461 118.097 127.581 109.774 128.266L100.296 128.847C96.8819 128.988 93.4132 129.061 89.9041 129.061C86.4886 129.061 83.1282 129 79.8317 128.88L70.2911 128.353C61.949 127.725 54.1434 126.685 47.0917 125.239L39.9607 123.568C38.3974 123.154 36.8455 122.704 35.3064 122.218C16.3222 116.202 3.82609 106.279 3.82609 96.3865C3.82609 86.8082 15.4661 77.5742 33.6932 71.6817C35.7301 71.0232 37.8585 70.4047 40.0643 69.8235L47.1933 68.1443C54.3189 66.6472 62.0693 65.5298 70.166 64.8339L79.7705 64.2154C86.5492 63.921 93.3392 63.9223 100.118 64.2191L109.692 64.8426C117.812 65.5491 125.568 66.6858 132.711 68.2148L139.831 69.9227Z"
						stroke="#00D8FF"
						strokeWidth="1"
					/>
					<path
						id="ring2"
						d="M91.8267 39.9871C93.1875 38.6233 94.5789 37.2932 95.9997 35.9979C110.436 22.8629 124.495 17.105 132.82 21.9116C141.502 26.924 143.57 42.9457 138.953 63.069C138.709 64.132 138.441 65.212 138.156 66.3051L136.096 73.3308C133.855 80.27 130.893 87.6392 127.325 95.1892L123.089 103.688C121.504 106.715 119.833 109.756 118.078 112.795C116.37 115.753 114.637 118.632 112.885 121.427L107.658 129.426C102.944 136.337 98.1405 142.576 93.3618 147.96L88.3496 153.3C87.2094 154.447 86.0436 155.566 84.8534 156.656C70.1508 170.089 55.3096 175.949 46.7424 171.003C38.4474 166.214 36.2705 151.516 40.2809 132.785C40.7292 130.692 41.2577 128.539 41.8573 126.338L43.9675 119.325C46.2338 112.405 49.1413 105.135 52.587 97.7748L56.8536 89.1478C59.988 83.13 63.3841 77.2504 67.0305 71.5283L72.3576 63.5486C77.0295 56.8695 81.8918 50.7212 86.7875 45.2995L91.8267 39.9871Z"
						stroke="#00D8FF"
						strokeWidth="1"
					/>
					<path
						id="ring3"
						d="M41.8994 66.5927C41.3987 64.7323 40.9425 62.8622 40.5312 60.9841C36.3742 41.9142 38.4171 26.8599 46.7424 22.0533C55.4241 17.0409 70.3335 23.2606 85.452 37.3211C86.2508 38.0636 87.0522 38.8356 87.8561 39.6294L92.9106 44.9259C97.7994 50.3369 102.701 56.5862 107.455 63.4515L112.697 71.3691C114.526 74.2555 116.324 77.223 118.078 80.2619C119.786 83.2198 121.413 86.1606 122.958 89.0753L127.271 97.6014C130.899 105.14 133.901 112.419 136.174 119.25L138.293 126.261C138.716 127.821 139.102 129.39 139.451 130.966C143.733 150.415 141.388 166.199 132.82 171.145C124.525 175.934 110.708 170.47 96.4919 157.632C94.9032 156.197 93.3033 154.663 91.6971 153.043L86.6784 147.709C81.819 142.286 76.9761 136.133 72.3251 129.469L66.9872 121.461C63.3429 115.737 59.949 109.856 56.8168 103.837L52.5697 95.2342C49.1214 87.8486 46.228 80.5636 43.9805 73.613L41.8994 66.5927Z"
						stroke="#00D8FF"
						strokeWidth="1"
					/>
					{/* 軌道アニメーション */}
					<AnimationCircle pathId="ring1" durSec={5} />
					<AnimationCircle pathId="ring2" durSec={7} isReverse />
					<AnimationCircle pathId="ring3" durSec={10} isReverse />
				</g>
			</svg>
		</div>
	)
}

type AnimationCircleProps = {
	pathId: string
	durSec?: number
	isReverse?: boolean
}

const AnimationCircle: VFC<AnimationCircleProps> = props => {
	const { pathId, durSec = 5, isReverse = false } = props

	return (
		<circle r="5" fill="#00D8FF">
			<animateMotion
				dur={`${durSec}s`}
				repeatCount="indefinite"
				keyPoints={isReverse ? '1;0' : '0;1'}
				keyTimes="0;1"
				calcMode="linear">
				<mpath xlinkHref={`#${pathId}`} />
			</animateMotion>
		</circle>
	)
}

const styles = {
	container: css`
		display: flex;
		justify-content: center;
		align-items: center;
		width: 500px;
		height: 500px;
	`
}
```

<!-- =============================================== -->
# まとめ
<!-- =============================================== -->
CSSやJSを使わないでも、基本的なアニメーションならSVGだけで実装できることを知りました。

<!-- =============================================== -->
# おまけ
<!-- =============================================== -->
### SVG 活用例
https://sample.atmarkit.jp/fux/1207/02/sample.xhtml#sample04

### SVG 作成ツール
Figma が便利です。
ブラウザ上で動作するのでインストールする必要がないですし、アカウント登録すればFreeで使用できます。

https://www.figma.com/

:::note info
プラグインとして <strong>iconify</strong> を導入すると、色々なアイコンのSVGを使うことができます。
:::

* プラグインの導入方法
![スクリーンショット 2021-09-16 150850.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/80f916fe-da08-c7ae-c801-098fab634b2e.png)
![スクリーンショット 2021-09-16 151101.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/76a0e266-53e5-385c-9e1d-b2cf312f102a.png)

* アイコンの使用方法
![スクリーンショット 2021-09-16 151431.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/6b894bc7-bba2-c3f1-4d33-a7b8ec417d45.png)

![スクリーンショット 2021-09-16 151523.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/5eb52f7f-5e22-feb3-598d-e2f2234dda51.png)

<!-- =============================================== -->
# 参照
<!-- =============================================== -->

https://atmarkit.itmedia.co.jp/ait/articles/1207/02/news142_2.html