SVGで日本地図のエリア別にホバー処理できるようにする

こんにちは、ウェブエンジニアのmasakichiです。
需要があるかわからない程度のtipsなどを書いています。

今回はSVGを利用して、地図のエリアごとにをホバー処理させるという実装をしました。（説明むずい）

# 完成形はこちらです
ちょっと何言ってんのわからないと思いますので、こんな感じです↓

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="XWejdXW" data-user="masakichi" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/masakichi/pen/XWejdXW">
  Untitled</a> by masakichi (<a href="https://codepen.io/masakichi">@masakichi</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

#なぜSVGを使うのか？
通常のpng画像などだとhover時の選択範囲をうまく指定できずに、意図した挙動になりません。
しかしSVGで作ったデータならpathの範囲内ごとにしっかりとhoverを当てることができます。

では実際に作り方を順番に書いていきます。

#SVGデータを作る
まずイラレからSVGデータをつくり書き出します。
今回はフリー素材の日本地図を使いました。
下記のように九州エリアなどpathが離れてしまっているところは、グルーピングしておきます。
![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1006362/30baaa5d-1919-2198-8922-6fd88d9dbe83.png)

#SVGを貼り付ける
書き出したSVGデータをHTMLファイルに貼り付けます。
ただこれだと、すごく見づらいので整形します。

```html:HTML
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1269.31 1104.19"><defs><style>.cls-1{fill:#1eaa39;}</style></defs><g id="レイヤー_2" data-name="レイヤー 2"><g id="日本"><path id="北海道" class="cls-1" d="M1224.16,0H959.11A45.29,45.29,0,0,0,914,45.15V257.93a35.43,35.43,0,0,0,35.33,35.32h56.53a35.43,35.43,0,0,0,35.33-35.32,21.64,21.64,0,0,1,.25-3.32c.53-3.39,2.07-10.14,6.26-14.57,4.76-5,15.06-4.55,15.06-4.55h161.44a45.29,45.29,0,0,0,45.15-45.15V45.15A45.29,45.29,0,0,0,1224.16,0Z"/><g id="九州"><path class="cls-1" d="M60.52,948.89H22.44C10.1,948.89,0,961.46,0,976.82v99.45c0,15.36,10.1,27.92,22.44,27.92H60.52c12.34,0,22.44-12.56,22.44-27.92V976.82C83,961.46,72.86,948.89,60.52,948.89Z"/><path class="cls-1" d="M294,661.82H173.76c-31.7,0-57.65,24.55-57.65,54.56l-.27,313.76c0,16.13,13.95,29.33,31,29.33h99.59c17.05,0,31-13.2,31-29.33v-25c0-20.75,6.78-24.39,17.19-24.39h5.09c40.85,0,52-16,52-54.56V716.38C351.66,686.37,325.72,661.82,294,661.82Z"/></g><rect id="四国" class="cls-1" x="386.34" y="858.25" width="229.75" height="138.76" rx="22.98"/><g id="本州"><path class="cls-1" d="M1172.21,270.46h-64.49a32.94,32.94,0,0,0-32.85,32.85v24.91a13.2,13.2,0,0,1-3.59,9.41c-4,4-11.26,3.67-11.26,3.67H958.76A44.93,44.93,0,0,0,914,386.1V605.36a17.34,17.34,0,0,1-5.32,13.11c-6.08,6.08-14.47,5.33-14.47,5.33H818.71s-15.05.8-20.79-4.95c-6.22-6.21-5.68-18-5.68-18V586.32a40.8,40.8,0,0,0-40.69-40.68H741a40.79,40.79,0,0,0-40.68,40.68v17.8s.39,6.26-6.52,13.17-13.21,6.51-13.21,6.51H436.06a49.86,49.86,0,0,0-49.72,49.72V799a26.75,26.75,0,0,0,26.74,26.75H602.14A50.48,50.48,0,0,1,652.47,876v94.23A26.74,26.74,0,0,0,679.21,997h295.1a15.12,15.12,0,0,0,15.13-15.13V966.42c0-15.36-4.2-46.9,30.85-46.9h45.48a41.77,41.77,0,0,1,41.65,41.64V981s-.25,8.36,3.68,12.28,11.35,3.69,11.35,3.69h32.89a49.86,49.86,0,0,0,49.72-49.72v-644A32.94,32.94,0,0,0,1172.21,270.46Z"/><rect class="cls-1" x="818.92" y="534.05" width="64.83" height="61.35" rx="18.52"/></g></g></g></svg>
```

#SVGのコードを整形する
以下の点に注意しながら整形します

- タグごとに改行つける
- `<defs>`タグのstyleはCSSへ転機
- 北海道、九州、四国、本州のそれぞれのエリアごとに`<g>`タグでグループ化しておく
- 不要な`<g>`タグなどがあったら削除しておく

整形したのがこちら。だいぶ見やすくなりました。

```html:HTML
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1269.31 1104.19">
	<g id="japan">
		<g id="hokkaido" class="maps">
			<path d="M1224.16,0H959.11A45.29,45.29,0,0,0,914,45.15V257.93a35.43,35.43,0,0,0,35.33,35.32h56.53a35.43,35.43,0,0,0,35.33-35.32,21.64,21.64,0,0,1,.25-3.32c.53-3.39,2.07-10.14,6.26-14.57,4.76-5,15.06-4.55,15.06-4.55h161.44a45.29,45.29,0,0,0,45.15-45.15V45.15A45.29,45.29,0,0,0,1224.16,0Z"/>
		</g>
		<g id="kyushu" class="maps">
			<path d="M60.52,948.89H22.44C10.1,948.89,0,961.46,0,976.82v99.45c0,15.36,10.1,27.92,22.44,27.92H60.52c12.34,0,22.44-12.56,22.44-27.92V976.82C83,961.46,72.86,948.89,60.52,948.89Z"/>
			<path d="M294,661.82H173.76c-31.7,0-57.65,24.55-57.65,54.56l-.27,313.76c0,16.13,13.95,29.33,31,29.33h99.59c17.05,0,31-13.2,31-29.33v-25c0-20.75,6.78-24.39,17.19-24.39h5.09c40.85,0,52-16,52-54.56V716.38C351.66,686.37,325.72,661.82,294,661.82Z"/>
		</g>
		<g id="shikoku" class="maps">
			<rect x="386.34" y="858.25" width="229.75" height="138.76" rx="22.98"/>
		</g>
		<g id="honshu" class="maps">
			<path d="M1172.21,270.46h-64.49a32.94,32.94,0,0,0-32.85,32.85v24.91a13.2,13.2,0,0,1-3.59,9.41c-4,4-11.26,3.67-11.26,3.67H958.76A44.93,44.93,0,0,0,914,386.1V605.36a17.34,17.34,0,0,1-5.32,13.11c-6.08,6.08-14.47,5.33-14.47,5.33H818.71s-15.05.8-20.79-4.95c-6.22-6.21-5.68-18-5.68-18V586.32a40.8,40.8,0,0,0-40.69-40.68H741a40.79,40.79,0,0,0-40.68,40.68v17.8s.39,6.26-6.52,13.17-13.21,6.51-13.21,6.51H436.06a49.86,49.86,0,0,0-49.72,49.72V799a26.75,26.75,0,0,0,26.74,26.75H602.14A50.48,50.48,0,0,1,652.47,876v94.23A26.74,26.74,0,0,0,679.21,997h295.1a15.12,15.12,0,0,0,15.13-15.13V966.42c0-15.36-4.2-46.9,30.85-46.9h45.48a41.77,41.77,0,0,1,41.65,41.64V981s-.25,8.36,3.68,12.28,11.35,3.69,11.35,3.69h32.89a49.86,49.86,0,0,0,49.72-49.72v-644A32.94,32.94,0,0,0,1172.21,270.46Z"/><rect class="cls-1" x="818.92" y="534.05" width="64.83" height="61.35" rx="18.52"/>
		</g>
	</g>
</svg>
```

スタイリングはCSSファイルで管理します。

```scss:SCSS
svg{
	display: block;
	max-width: 400px;
	margin: 0 auto;
	.maps{
		fill:#1eaa39;
		transition: fill .3s;
	}
}
```

#色を変えるためのclassを追加
`.maps`クラスに`.active`クラスがついたら色が変わるようにCSSを追記します

```scss:SCSS
svg{
	display: block;
	max-width: 400px;
	margin: 0 auto;
	.maps{
		fill:#1eaa39;
		transition: fill .3s;
	}
	//activeクラスがついたら色を変更
	.maps.active{
		fill:#f5f825;
	}
}
```

#JSでクラス付け替え処理を追加

ホバー時のactiveクラス付け替えはjavascriptで実装します

```javascript:JavaScript
$(function(){
    let maps = $('#japan').find('.maps');
    maps.mouseenter(function(){
        $(this).addClass('active');
    })
    maps.mouseleave(function(){
        $(this).removeClass('active');
    })
});
```

これでホバー時に色が変わるようになりました。
次は地図の下に、それぞれのエリア名を表示します。

#エリア名表示のためにタグを追加
まず、svgの下にエリア名を表示させる`<p>`タグを追加します。
また`maps`クラスにはエリア名を持ったデータ属性を与えておきます。

```html:HTML
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1269.31 1104.19">
	<g id="japan">
		<g id="hokkaido" data-area="北海道" class="maps"><!-- データ属性追加 -->
			<path d="M1224.16,0H959.11A45.29,45.29,0,0,0,914,45.15V257.93a35.43,35.43,0,0,0,35.33,35.32h56.53a35.43,35.43,0,0,0,35.33-35.32,21.64,21.64,0,0,1,.25-3.32c.53-3.39,2.07-10.14,6.26-14.57,4.76-5,15.06-4.55,15.06-4.55h161.44a45.29,45.29,0,0,0,45.15-45.15V45.15A45.29,45.29,0,0,0,1224.16,0Z"/>
		</g>
		<g id="kyushu" data-area="九州" class="maps"><!-- データ属性追加 -->
			<path d="M60.52,948.89H22.44C10.1,948.89,0,961.46,0,976.82v99.45c0,15.36,10.1,27.92,22.44,27.92H60.52c12.34,0,22.44-12.56,22.44-27.92V976.82C83,961.46,72.86,948.89,60.52,948.89Z"/>
			<path d="M294,661.82H173.76c-31.7,0-57.65,24.55-57.65,54.56l-.27,313.76c0,16.13,13.95,29.33,31,29.33h99.59c17.05,0,31-13.2,31-29.33v-25c0-20.75,6.78-24.39,17.19-24.39h5.09c40.85,0,52-16,52-54.56V716.38C351.66,686.37,325.72,661.82,294,661.82Z"/>
		</g>
		<g id="shikoku" data-area="四国" class="maps"><!-- データ属性追加 -->
			<rect x="386.34" y="858.25" width="229.75" height="138.76" rx="22.98"/>
		</g>
		<g id="honshu" data-area="本州" class="maps"><!-- データ属性追加 -->
			<path d="M1172.21,270.46h-64.49a32.94,32.94,0,0,0-32.85,32.85v24.91a13.2,13.2,0,0,1-3.59,9.41c-4,4-11.26,3.67-11.26,3.67H958.76A44.93,44.93,0,0,0,914,386.1V605.36a17.34,17.34,0,0,1-5.32,13.11c-6.08,6.08-14.47,5.33-14.47,5.33H818.71s-15.05.8-20.79-4.95c-6.22-6.21-5.68-18-5.68-18V586.32a40.8,40.8,0,0,0-40.69-40.68H741a40.79,40.79,0,0,0-40.68,40.68v17.8s.39,6.26-6.52,13.17-13.21,6.51-13.21,6.51H436.06a49.86,49.86,0,0,0-49.72,49.72V799a26.75,26.75,0,0,0,26.74,26.75H602.14A50.48,50.48,0,0,1,652.47,876v94.23A26.74,26.74,0,0,0,679.21,997h295.1a15.12,15.12,0,0,0,15.13-15.13V966.42c0-15.36-4.2-46.9,30.85-46.9h45.48a41.77,41.77,0,0,1,41.65,41.64V981s-.25,8.36,3.68,12.28,11.35,3.69,11.35,3.69h32.89a49.86,49.86,0,0,0,49.72-49.72v-644A32.94,32.94,0,0,0,1172.21,270.46Z"/><rect class="cls-1" x="818.92" y="534.05" width="64.83" height="61.35" rx="18.52"/>
		</g>
	</g>
</svg>
<p id="areaName"></p><!-- ここにエリア名を出す -->
```

#JSにエリア名表示の処理を追加
最後にエリア名の取得と表示をJavaScriptで記述すれば完成です。

```javascript:JavaScript
$(function(){
    let maps = $('#japan').find('.maps');

    maps.mouseenter(function(){
        $(this).addClass('active');
        let areaName = $(this).data('area');
        $('#areaName').text(areaName);
    })
    maps.mouseleave(function(){
        $(this).removeClass('active');
        $('#areaName').text("");
    })
});
```

SVGって便利ですね!
もっといろんなことできそうなので、どこかのタイミングで勉強したいと思います。