【Vue】喘ぎ声メーカー作ってみた

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605100/71fb8117-adef-6093-77c4-c1a228c760c9.png)
[スーパー喘ぎ声メーカー](https://mitaru.github.io/superxxxmaker/)
用意した単語をシャッフルして**喘ぎ声を生成するツール**を作ってみました。
喘ぎ声に限らず、好きな単語を入れて遊べます。
#何故作ろうと思ったのか

BL小説書きの友人が「**この喘ぎ声の組み合わせさっきも書いたな？
となるので自動生成してくれるツールがほしい**」と言っていたので。
BLに限らずいやらしい小説を書く方への負担を少しでも減らせないかと思い、作成にいたりました。
あとVueの勉強になりそうだと思ったので。
筆者は文系のwebデザイナー（見習い）であり、プログラミングの分野は勉強中です。

#使ったもの
 - HTML
 - CSS
 - JavaScript
 - [clipboard.js](https://clipboardjs.com/)
 - Vue.js
 - jQuery
 - Bootstrap4
 - [Font Awesome](https://fontawesome.com/)

#制作の流れ
##プロトタイプ①
![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605100/9ee58ff6-3bab-188b-f55a-bd9628a0f55a.png)

```javascript:
<button onclick="shuffle()">生成！</button>
<p id="result1"></p>
<p id="result2"></p>
<p id="result3"></p>
<p id="result4"></p>
<p id="result5"></p>

<script>
Array.prototype.shuffle = function () {
  var i = this.length;
  while (i) {
    var j = Math.floor(Math.random() * i);
    var t = this[--i];
    this[i] = this[j];
    this[j] = t;
  }
  return this;
}

var words = ['あっ…', 'やっ', 'そん', '…っ', 'だめ', '♡',];

function shuffle() {
  words.shuffle();
  document.getElementById('result1').innerText = words.join('');
  words.shuffle();
  document.getElementById('result2').innerText = words.join('');
  words.shuffle();
  document.getElementById('result3').innerText = words.join('');
  words.shuffle();
  document.getElementById('result4').innerText = words.join('');
  words.shuffle();
  document.getElementById('result5').innerText = words.join('');
}

for (var i = 0; i < words.length; i++) {
  var li = document.createElement('li');
  li.textContent = words[i];
  document.getElementById('list').append(li);
}
</script>
```
まずHTMLとJavaScriptのみでプロトタイプを作成。
「生成！」ボタンを押すと配列をシャッフルした結果が表示されます。
決め打ちのデータしか組み合わせられませんが、大体のイメージができあがりました。

ちなみに「**やっそん**（おそらく「やめて、そんな」→「やっ…そん…」→「やっそん」と転じた）」は
一部界隈のスラングで**BLの性行為のことを指します。**永遠に使えないムダ知識をあなたに。

使用例：出会って5秒でやっそん

##プロトタイプ②
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605100/6ac07edf-09db-02ab-806f-9b204217db13.png)
Vue.jsでよくあるTodoアプリのコードを元にプロトタイプ第二弾を作成。
入力したデータを組み合わせられるようになった点はいい感じです。

```javascript
<div id="app" class="container">
  <h1 class="header">喘ぎ声メーカー</h1>
  <div class="left">
    <h1>使用する単語</h1>
    <form @submit.prevent="addItem">
      <input type="text" v-model="newItem">
      <input type="submit" value="追加">
    </form>
    <ul>
      <li v-for="(word, index) in words">
        {{ word }}
        <span @click="deleteItem(index)" class="command">[x]</span>
      </li>
    </ul>
  </div>
  <div class="right">
    <h1>結果</h1>
    <ul>
      <li v-for="(word, index) in words.length">
        <span v-for="n in words.length" class="result">{{ random() }}</span>
      </li>
    </ul>
  </div>
</div>

<script>
  (function () {
    'use strict';

    var vm = new Vue({
      el: '#app',
      data: {
        newItem: '',
        words: [
          'あっ',
          'やっ',
          'んっ…',
          'っ',
          '♡',
          '、',
        ]
      },
      methods: {
        addItem: function () {
          this.words.push(this.newItem);
          this.newItem = '';
        },
        deleteItem: function (index) {
          this.words.splice(index, 1);
        },
        random: function () {
          const rnd = Math.floor(Math.random() * this.words.length);
          return this.words[rnd];
        }
      }
    });
  })();
</script>
```

ただ現在のrandomメソッドだと同じ単語を何度も使ってしまうため、「あっあっあっ」と
**ネフェルピトーに脳を弄くられるポックル**みたいになってしまいます。
配列をシャッフルするコードに書き換えなければなりません。

##モックアップ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605100/20c53104-4746-f393-bfdf-147fa5793823.png)
Bootstrap4で見た目だけのものを作成。レスポンシブにも対応です。

##完成品
完成品がこちらです！→[スーパー喘ぎ声メーカー](https://mitaru.github.io/superxxxmaker/)

```javascript

var vm = new Vue({
  el: '#app',
  data: {
    newItem: '',
    array: [
      'やっ',
      'あっ',
      'んっ…',
    ],
  },
  methods: {
    addItem: function () {
      this.array.push(this.newItem);
      this.newItem = '';
    },
    deleteItem: function (index) {
      this.array.splice(index, 1);
    },
    shuffle: function (array) {
      var sArray = [];
      sArray = array.slice(0, array.length);
      var n = sArray.length, t, i;
      while (n) {
        i = Math.floor(Math.random() * n--);
        t = sArray[n];
        sArray[n] = sArray[i];
        sArray[i] = t;
      }
      return sArray.join('');
    },
    reset: function () {
      this.array = []
    }
  }
});
```

長くなるのでVue部分だけ掲載。（[HTML含む全文はgithubで公開してます](https://github.com/mitaru/superxxxmaker)）

```javascript
shuffle: function (array) {
  var n = array.length, t, i;
  while (n) {
    i = Math.floor(Math.random() * n--);
    t = array[n];
    array[n] = array[i];
    array[i] = t;
  }
  return array.join('');
}
```

これは過去のコードなのですが、このメソッドだと
シャッフル実行時**配列そのものもシャッフル**されてしまいます。下図参照。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605100/b38db720-1795-0510-21aa-288c7fb82911.png)

```javascript
shuffle: function (array) {
  var sArray = [];
  sArray = array.slice(0, array.length);
  var n = sArray.length, t, i;
  while (n) {
    i = Math.floor(Math.random() * n--);
    t = sArray[n];
    sArray[n] = sArray[i];
    sArray[i] = t;
  }
  return sArray.join('');
}
```

コピーした配列にシャッフル処理をするように書き換えたら
**単語のリストはシャッフルされなくなりました。**
この対処法が正しいかはわかりませんが…

![list.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605100/f5f4dec6-3139-6f9b-7ed4-2b0f189cb196.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605100/33482280-1d92-4882-aef4-a07d4d9ff12d.png)
また生成結果を表示するリストは、**idを個別に用意するため**に
clipboard-target1～clipboard-target10まで**わざわざ一つ一つ`<li>`要素を作成**していました。
何故idを個別に用意する必要があるのかと言うと、
[clipboard.js](https://clipboardjs.com/)で特定のdivのテキストをコピーするために
**個別のターゲットを指定しなければならない**からです。
`data-clipboard-target`の指定先がid名になっているのがお分かりいただけると思います。

でもVueならもっとスッキリ書けるんじゃないか？とよく考えたら、
`v-for`のループ処理でなんとかできそうだと思い至る。

```html:
<li v-bind:id="`clipboard-target${n}`" class="clipboard-target mb-2" v-for="n in 10">
  <span>{{ shuffle(array) }}</span>
  <div class="btn btn-danger btn-sm ml-3 btn-clipboard" data-toggle="tooltip"
    v-bind:data-clipboard-target="`#clipboard-target${n}`">
    コピー
  </div>
</li>
```

`v-bind`と`v-for`でidとdata-clipboard-targetが連番になった`<li>`要素を10個作ることができました。
v-for="n in 10"の10の部分を変更すれば、喘ぎ声セットを10個でも100個でも作成できますね！
JavaScriptでやったら多分めちゃくちゃ面倒くさい処理ですよね…
ありがとうVue。

##感想
 - 明確な目的があったためVue勉強のモチベーションが保ててよかった
 - 友達にややウケした

##課題・問題点
 - ~~双方向バインディングすぎてEnterを押すたびに結果が再生成される~~→コメントのおかげで解決
 - ~~最初のイメージに合わせる知識がなかった（本当は「生成」ボタン押下時に結果が表示されるようにしたかった）~~→コメントのおかげで解決
 - 一度リセットしてから再度単語を追加して生成すると、ツールチップが表示されない(コピー自体は問題なくできる)
 - 本当はVueのコンポーネント機能を使いたかったが調べてもわからなかった

GitHubでコードを公開しておりますので、アドバイスいただけたら嬉しいです。
https://github.com/mitaru/superxxxmaker
コード見たら私がマジの初心者であることはお分かりになるかと思います…
プルリクお待ちしております。

## 追記
いただいたコメントを元に改造を進めております。

**ver1.1(2020-03-19)**
・配列がLocalStorageでキャッシュされるようになりました
・代わりに初期配列を表示できなくなりました
**ver1.2(2020-03-20)**
・「生成！」ボタン押下時に結果が表示されるようになりました
・なぜかツールチップが表示できなくなりました 
**ver1.3(2020-03-21)**
・軽微な修正