【初心者】WebSpeechAPIと『BonJovi』で童心に帰ろう【ネタ】

## 箸が転がっても面白い
父 「アレクサ、**ボンジョビ**かけて」
アレクサ 「ｯｽ..**ｼｬｯﾌﾙ再生**しﾏｽ...」

子 「おとうさん、ボンジョビって何？歌手？」
父 「バンドだよ。昔の。アメリカの。」
　 「歌とか楽器の集まりの名前が**ボンジョビ**で、歌う人の名前が、**ジョンボンジョビ**。」

子 「ジョンジョビ？」
父 「ボンジョビの、ジョンボンジョビ。」
子 「ボンボンジョビ！ｹﾗｹﾗ」
父 （子供は**単純**でいいな・・・）

子 「おとうさん、もっと作って！**全部言**って！！」
父 （全部ってなんだよ・・・）

子 「ジョンボンジョビ！ジョンジョンボビ！合ってる？ｹﾗｹﾗ」
父 （**シャッフル**すればいいのか・・・）

## 作ったやつ



https://twitter.com/mitoreterusuki/status/1663210794480439302

### 環境
・Vue.js
・Web Speech API
・Bootstrap
・Netlify

### 作り方

やりたいことはざっくり以下の通りです。

1. "Jon","Bon","Jovi","Bovi" からランダムに3つ入力
2. 入力結果をWeb Speech APIで発話させる

#### 1. 実装
＜CodePen＞

違う言葉で遊びたい場合は、CodePenに登録して**Fork**すると簡単に変更できます。

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="yLRrxgv" data-user="mitoreterusuki" 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/mitoreterusuki/pen/yLRrxgv">
  Joke with Vue.js + BootStrap</a> by mitoreterusuki (<a href="https://codepen.io/mitoreterusuki">@mitoreterusuki</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>


ボタン、フィールドの各要素は**Bootstrap**を使って装飾しています。

<details><summary>html コード例</summary>

```html:index.html
<div id="app" class="text-center">
  <p class="display-4">
<img src="./title.png" class="img-fluid">
  </p>
  <div class="mb-3">
    <button class="btn btn-dark btn-lg mr-2" @click="addToMessage('Jon')">Jon</button>
    <button class="btn btn-dark btn-lg mr-2" @click="addToMessage('Bon')">Bon</button>
    <button class="btn btn-dark btn-lg mr-2" @click="addToMessage('Jovi')">Jovi</button>
    <button class="btn btn-dark btn-lg mr-2" @click="addToMessage('Bovi')">Bovi</button>
    <button class="btn btn-warning btn-lg mr-2" @click="challenge">Challenge</button>
  </div>
  <p>
    <div class="form-floating mb-3">
    <input class="form-control form-control-lg" placeholder="It's my name" v-model="message" />
    <label for="floatingInput">It's my name</label>
</div>
    <button class="btn btn-primary btn-lg ml-2" @click="say">Speak</button>
    <a :href="getTweetURL()" target="_blank" rel="noopener noreferrer" class="btn btn-info btn-lg">Tweet</a>
    <button class="btn btn-secondary btn-lg ml-2" @click="clear">Clear</button>
  </p>
</div>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js'></script><script  src="./script.js"></script>

```
</details>


・ メニュー上部の要素（入力）
![20230529_Header.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480342/bc902c3f-718f-76c7-8780-b879db397892.png)

各ボタンはクリックで文字列をフィールドに追加、**Challenge**ボタンで文字列のうち3つを**ランダム**にフィールドに追加しています。

・ メニュー下部の要素（出力・初期化）

![20230529_Footer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480342/a25d50d8-66ea-5d1b-a196-fadb664a88fe.png)

ボタンの機能は以下の通りです。
- Speak：フィールドの文字列をWeb Speech APIで読み上げ
- Tweet：フィールドの文字列をTwitterに投稿
- Clear：フィールドの文字列を削除

Web Speech APIによる音声読み上げは言語指定できますが、一通り試して
最もしっくりきた**日本語を指定**しております。

<details><summary>JavaScript コード例</summary>

```JavaScript:script.js
const app = new Vue({
  el: '#app',
  
  data: {
    message: '',
    options: ['Jon', 'Bon', 'Jovi', 'Bovi'],
  },
  
  mounted() {
    this.setupSpeech();
  },
  
  methods: {    
    async say() {
      return new Promise((res) => {
        this.speech.text = this.message;
        this.speech.onend = () => res();
        speechSynthesis.speak(this.speech);
      });
    },
    
    clear() {
      this.message = '';
    },
    
    addToMessage(text) {
      this.message += text + ' '; // 半角スペースを追加
    },
    
    challenge() {
      this.clear(); // messageフィールドをクリア
      
      const selectedOptions = this.getRandomOptions(3);
      this.message = selectedOptions.join(' ') + ' '; // 選択肢と半角スペースを追加
    },
    
    getRandomOptions(count) {
      const optionsCopy = this.options.slice();
      const selectedOptions = [];
      
      while (selectedOptions.length < count && optionsCopy.length > 0) {
        const randomIndex = Math.floor(Math.random() * optionsCopy.length);
        const option = optionsCopy.splice(randomIndex, 1)[0];
        selectedOptions.push(option);
      }
      
      return selectedOptions;
    },
      getTweetURL() {
    const tweetMessage = encodeURIComponent(this.message); // メッセージをURLエンコード
    return `https://twitter.com/intent/tweet?text=${tweetMessage}&url=jbjv.netlify.app`; // ツイートのURLを生成
  },
    
    setupSpeech() {
      this.speech = new SpeechSynthesisUtterance();
      this.speech.lang = 'ja-jp';
      window.speechSynthesis.onvoiceschanged = () => {
        const voices = window.speechSynthesis.getVoices();
        this.speech.voice = voices.find(voice => voice.lang == 'ja-JP');
        console.log(this.speech.voice);
      };
    }
  },
});
```

</details>

#### 2. 公開

**Netlify**を使って、Webアプリとして公開しましょう。

https://qiita.com/niihara_megumu/items/db8c53f0f5de9f377976




## おわりに

週末これ作ってたら、こどもは喜んだけど、妻には苦い顔をされました。

童心に帰るのは計画的に！