[約束のネバーランド] Vue.jsでミネルヴァさんのモールススタンプを描いてみる

私が今年最もハマった漫画「約束のネバーランド」で登場する、ミネルヴァさんのモールススタンプをVue.jsで作ってみました。

まずは完成したものをどうぞ…

<img width="462" alt="スクリーンショット 2019-12-18 0.13.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156563/fbaa47f5-8a09-8bde-dd87-ff55ba40c56d.png">

[こちら](https://minerva-morse-git-master.minojiro.now.sh/)で実際に遊べます
いらすとやさんのおかげもあって、なんだか本家よりもファンシーになっていると思います。

## コードの解説

https://github.com/minojiro/minerva-morse

まず、スタンプのSVGはVueのコンポーネントになっていて、propでメッセージを渡すとスタンプのSVGが作られます。

こんな感じです。

```vue
<MorseStamp message="sos" />
```

それでは、[MorseStampコンポーネントの中](https://github.com/minojiro/minerva-morse/blob/master/src/components/MorseStamp.vue)を解説していきます。
※ 解説のために一部コードを変更したりしています。

### メッセージのモールス化

`morse` というnpmパッケージを使っています。
アルファベットの文字列を簡単にモールス化してくれます。

```javascript
const morse = require('morse')
console.log(morse.encode('SOS')) // '... --- ...'
```

こんな感じで使えます。ありがたいですね。

### モールスの描画

![Group.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156563/3420d4a9-a788-623c-674b-63c3135a3a35.png)


コミックスの3巻でいくつか出てくるモールススタンプを見てみると、どうやらこんな感じのルールで描かれているようです。

 - 左上から時計回りに一周して読む
 - 1つの英単語がしっかり一周（360度）に収まっている
 - モールスで `.` と `-` があるうちで、 `.` は決まった長さ、 `-` は余った長さを均等に割った長さ

まずは `morse` から取得したモールスの文字列を配列にして、 `-` の長さを計算します。

```javascript
const DOT_AND_SPACE_WIDTH = 9 // `.` と ` ` の長さ

const morseString = morse.encode(this.message)
const chars = `${morseString} `.split('')
const barCount = chars.filter(char => char === '-').length
const dotAndSpaceCount = chars.length - barCount
const barWidth = (360 - DOT_AND_SPACE_WIDTH * dotAndSpaceCount) / barCount // ← `-` の長さ！
```

あとはそれぞれのモールス符号をSVGで描画するときのパラメータ計算していきます。

```javascript
const DOT_AND_SPACE_WIDTH = 9 // `.` と ` ` の幅
const MORSE_HEAD_DEGREE = -135 // 左斜め上から時計回りに読むらしい

let previousEndDegree = MORSE_HEAD_DEGREE

return chars.map(char => {
  const charWidth = char === '-' ? barWidth : DOT_AND_SPACE_WIDTH
  const startDegree = previousEndDegree
  const endDegree = startDegree + charWidth
  previousEndDegree = endDegree
  if (char === ' ') {
    return ''
  }
  // pathタグのd属性の値を取得
  return getPathTagDAttrValue(startDegree, endDegree)
}).filter(s => s !== '')
```

モールス符号の開始位置と終了位置の度数を受け取って、SVGの `<path>` のパラメーター（d属性）を返すところ

```javascript
const CHAR_OFFSET = 2 // 符号ごとの間隔

const degreeToRadian = (degree) => degree * Math.PI / 180

const getPathTagDAttrValue = (degree1, degree2) => {
  const radian1 = degreeToRadian(degree1 + CHAR_OFFSET)
  const radian2 = degreeToRadian(degree2 - CHAR_OFFSET)
  const x1 = Math.sin(radian1) * R + O
  const y1 = Math.cos(radian1) * R + O
  const x2 = Math.sin(radian2) * R + O
  const y2 = Math.cos(radian2) * R + O
  const f1 = (degree2 - degree1) >= 180 ? 1 : 0
  return `M ${y1},${x1} A ${R} ${R} 0 ${f1} 1 ${y2},${x2}`
}
```

ここまでできたら、あとはv-forでまわすだけ！

```vuejs

<template>
  <svg>
    <path
      :d="signPath"
      :key="signPath"
      v-for="signPath in signPathes"
    />
  </svg>
</template>
```

おまけに背景の色と、ふくろうの画像をつけます

```vuejs

<rect fill="#f6eace" />
<image xlink:href="@/assets/animal_fukurou.png" />
```

## Vue.js と SVG

今年はなにかとVue.jsでSVGを書くお仕事が多かったのですが、この組み合わせはかなり相性が良いなと思っています。
慣れ親しんだSVGにちょっとVue.jsのコードを書き加えるだけで気軽に動的なSVGが描画できますし、他のSVG描画系のライブラリを導入するよりも学習コストが低いのも嬉しいですよね。
アニメーションなどの表現の作り込みに関してはD3.jsやTweenMax.jsの方が得意ですが、データによって瞬時に変わる簡単なグラフなどは、Vue.jsの方がシンプルに書けそうです。

## 最後に

2020年は、約束のネバーランドのアニメの二期もあるとのことなので楽しみですね！
それではみなさん、

<img width="398" alt="スクリーンショット 2019-12-19 2.24.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156563/95abccb2-6f73-d17c-db27-705a8b54080e.png"><img width="397" alt="スクリーンショット 2019-12-19 2.24.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156563/8482e060-4cb9-5abd-8168-75f1e85176bb.png">

(merry christmas)