Vue.js / Web Speech API で作る、 PWA対応 英単語学習ソフト

この記事は[「PWA Advent Calendar 2019」](https://qiita.com/advent-calendar/2019/pwa)の18日目の記事です。

今年の春、Progressive Web Apps や Firebase の練習がてら、英単語学習ソフトを開発しました。
(が、そのまま放置していた)

作りっぱなしももったいないので、アドベントカレンダーに乗じてご紹介します。

* [ベーシック英単語800](https://basicenglish800.firebaseapp.com/)

以下のような特徴があります。

* Vue.js を利用したMPA(Multi-page Application)
* Progressive Web Apps 対応。Windows10/スマホにインストールして、オフラインで動作。
* 英単語の発音をクリックして確認できる (Web Speech API 利用)
* Firebase のHosting機能を利用して公開
* 選択肢と回答をランダムに生成。英単語アプリにありがちな「同じ選択肢と回答が繰り返され、出題パターンを覚えてしまう」ことがないようにした。(800の4乗x10問で、組み合わせは4兆通りぐらい？)

[![be800_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83767/53fcd6ec-fd96-d8c9-f0f8-de5dc9497bf6.png)](https://basicenglish800.firebaseapp.com/)


[![be800_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83767/ee963664-af01-4e5a-84c4-4034bfbdd420.png)](https://basicenglish800.firebaseapp.com/)





開発中のメモをもとに、いくつか備忘録を記述します。

## Web Speech API による英語音声の確認

アプリを起動すると、英単語と4つの選択肢が表示されます。英単語をクリック・タップすると、英語の発音を確認できます。
[Speech Synthesis API](https://developer.mozilla.org/ja/docs/Web/API/SpeechSynthesis) という、Web Speech APIの音声合成機能を利用しました。

```javascript
          pronounce: function () {

            // confirm English word's pronounciation

            let u = new SpeechSynthesisUtterance();
            u.lang = 'en-US';
            u.text = document.getElementById('englishWord').innerHTML;
            u.volume = "1";
            speechSynthesis.speak(u);

          }
```

※Speech Synthesis API の使い方については[拙稿「Web Speech API を 利用して 英単語の音声確認をするアプリを作る」](https://qiita.com/TakeshiNickOsanai/items/7a486085b039c2128217)にまとめました。


## Service Worker によるデータキャッシュ

html・CSS・効果音などのアセットファイル類を、Service Worker でまとめてキャッシュしています。

バージョンをキャッシュのキーとして登録。バージョン情報に更新があった場合、キャッシュパージ後、ファイルをキャッシュしなおすようにしました。

```javascript
const CACHE_NAME = `BasicEnglish800-${version}`;

---- 中略 ----

// Service Worker へファイルをインストール

self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function (cache) {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

// リクエストされたファイルが Service Worker にキャッシュされている場合
// キャッシュからレスポンスを返す

self.addEventListener('fetch', function (event) {
  if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin')
    return;
  event.respondWith(
    caches.match(event.request)
      .then(function (response) {
        if (response) {
          return response;
        }
        return fetch(event.request);
      }
      )
  );
});

// Cache Storage にキャッシュされているサービスワーカーのkeyに変更があった場合
// 新バージョンをインストール後、旧バージョンのキャッシュを削除する
// (このファイルでは CACHE_NAME をkeyの値とみなし、変更を検知している)

self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(keys => Promise.all(
      keys.map(key => {
        if (!CACHE_NAME.includes(key)) {
          return caches.delete(key);
        }
      })
    )).then(() => {
      console.log(CACHE_NAME + "activated");
    })
  );
});

```

※Service Workerの使い方や詳細については[拙稿「Progressive Web Apps (PWA) 学習者のメモ その1 　(Service Worker)」](https://qiita.com/TakeshiNickOsanai/items/8d012a128827c9db980d)にまとめました。



## Vue.js で問題を生成、回答記録を追跡

Vue.js を利用して、回答数を記録。
最初に１０問分の英単語・選択肢・回答を、ランダムに生成。
配列に単語・回答・選択肢のデータを格納。
問題を解いて「次へ」をクリック・タップすることで、配列を切り替え、次の問題を表示。
１問解くたびに、次の問題へ切り替え。

10問終了後に正誤のデータを確認し、回答を記録するようにしました。

```javascript

      const vm = new Vue({
        el: '#el',
        data() {
          return {
            arr: answerList,
            count: 0,
            choice: quizList,
            result: [],
          }
        },
        methods: {
          check: function (event) {

            // check user's answer each by each

            let target = document.getElementById("answerOptions");

            target.setAttribute('style', 'pointer-events: none;');
            let rightAnswer = this.arr[this.count].Japanese;
            let chosenAnswer = event.target.innerHTML;

            if (rightAnswer === chosenAnswer) {

              correctSound.play();
              message.innerHTML = "正解!";
              addAnswer("○");

            } else {

              wrongSound.play();
              message.innerHTML = "残念!";
              addAnswer("×");

            }

            this.count === 9 ? complete() : unComplete();
          },
```

※Vue.js を利用した回答の切り替え（＝配列の切り替え）については、[拙稿「Vue.js で 配列とJSONの切り替え表示を行う」](https://qiita.com/TakeshiNickOsanai/items/4c9667ba6e5e59f99bb3)にまとめました。



## 振り返り

一つ一つは単純なコードですが、組み合わせることで、それなりにアプリとして形になった気がしました。

できれば

* Firebase のユーザー認証を利用して、リアルタイムDBに回答記録を保存
* 全アプリユーザーの間で、どの単語の誤答率が高いか、統計を取る

までやってみたかったのですが、時間切れで実装できず。
Firebaseの利用はHostingのみとなりました。

そのうち時間を作ってチャレンジしてみたいと思います。

## 備考

以下のサイトのデータ・情報を元に開発しました。

* 英単語データ
    * 
[「800ベーシック英単語」](https://www.catch.jp/wiki/index.php?english%2F800_Basic_English) 
* ランダムな配列生成ロジック
    * [「m2」](https://miya2000.hatenadiary.org/entry/20080607/p0)