中級 JavaScript キーボード SVG 音声合成

タイピング練習ゲームを作る

お題の文字を正しく打つと次に進む、タイピング練習ゲームです。キーボードの図を SVG で描き、次に押すキーを光らせます。英語・日本語かな・ハングル・注音に対応しています。

このサンプルは2017〜2018年ごろに作成したものです。PHPのバージョンが上がり、そのままでは動かなくなっていたため、2026年に PHP 8.3 で動くように書き直し、解説も新しくしました。

DEMO 実際に動かしてみよう

下の枠の中を一度クリックしてから、キーボードの好きなキーを押すとスタートします。次に押すキーが緑色で光ります。(パソコンのキーボード専用です)

解説コードを読んでみよう

このサンプルでわかること

  • keydown イベントで、押されたキーを調べる方法
  • 「お題の何文字目まで打てたか」を変数で管理する考え方
  • SVG で描いた図形の色を JavaScript から変える方法
  • ブラウザの音声合成(SpeechSynthesisUtterance)でお題を読み上げる方法

使っているファイル

  • english.html英語版のゲーム本体(HTML・SVG・JavaScript)
  • alphabet.js英語キーボードの「キーの番号 ⇔ 文字」の対応表
  • jp-kana.html / ja-kana.js日本語かな入力版と、かな配列の対応表
  • korean.html / hangul.jsハングル版と、ハングル配列の対応表
  • tw.html / tw-bopomofo.js注音(繁体字)版と、注音配列の対応表
  • index.phpこのページ。タブで言語を切り替えて iframe に表示

4つの言語版は、プログラムの部分がほぼ同じです。違うのは「お題」と「キーの対応表(○○.js)」だけなので、ここでは英語版(english.html)で説明します。

1. 画面の部品を用意する

お題の意味(#m)、お題(#w)、補足(#c)、点数とタイマーを表示する場所を HTML で用意します。中身は空にしておき、JavaScript から書き込みます。

english.html(30〜38行目) HTML
<body>
  <p id="m"></p>
  <p id="w"></p>
  <p id="c"></p>
  <p class="info">
    タイプ数: <span id="score">0</span>,
    ミスタッチ数: <span id="miss">0</span>,
    経過時間: <span id="timer">00:00.000</span>
  </p>

2. キーと文字の対応表を作る

キーボードのキーには、それぞれ番号(キーコード)があります。たとえば A は 65 です。どのキーを押すとどの文字になるか、Shift が必要かを表にしたのが alphabet.js です。

alphabet.js(1〜6行目) JavaScript
var list =
[
{    key: '49', value: '1', shift: false},
{    key: '50', value: '2', shift: false},
{    key: '51', value: '3', shift: false},
{    key: '52', value: '4', shift: false},

日本語かな版では、同じキー番号に「ぬ」「ふ」などのかな文字を対応させています。表を差し替えるだけで別の言語のゲームになる、というのがこの作りのよいところです。

3. キーボードの図を SVG で描く

キーボードの絵は、SVG の <rect>(四角)と <text>(文字)を並べて描いています。それぞれの四角には id="key_65" のようにキー番号入りの id をつけておきます。こうすると、あとで「65番のキーを光らせる」が簡単にできます。

english.html(40〜45行目) HTML
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 670 250" style="width:100%;max-width:670px;height:auto">
<rect id="key_49" x="0" y="0" width="48" height="48" rx="5" ry="5" fill="none" stroke="#000000" />
<rect id="key_50" x="50" y="0" width="48" height="48" rx="5" ry="5" fill="none" stroke="#000000" />
<rect id="key_51" x="100" y="0" width="48" height="48" rx="5" ry="5" fill="none" stroke="#000000" />
<rect id="key_52" x="150" y="0" width="48" height="48" rx="5" ry="5" fill="none" stroke="#000000" />
<rect id="key_53" x="200" y="0" width="48" height="48" rx="5" ry="5" fill="none" stroke="#000000" />

4. お題を用意する

お題は配列 words にまとめます。1つのお題は m(意味)・w(打つ文字)・c(補足)の3つを持つオブジェクトです。

english.html(183〜190行目) JavaScript
var words = 
[
{m:'こんにちは、世界', w:'Hello World', c:''},
{m:'さあ、始めよう', w:'It\'s show time.', c:''},
{m:'おはよう!', w:'Good morning!', c:''},
{m:'上の段', w:'QWERTYUIOP', c:'キーボードの上の段のキー'},
{m:'真ん中の段', w:'ASDFGHJKL', c:'ホームポジションの段'}
]

5. 次のお題を表示して、読み上げる

setTarget() は、次のお題を画面に表示する関数です。最初に SpeechSynthesisUtterance でお題を読み上げ、次に押すべきキーを緑色(#98FB98)に塗ります。

english.html(280〜303行目) JavaScript
//*NextPhrase
    function setTarget(next) {

     var synthes = new SpeechSynthesisUtterance(words[next].w);
     synthes.lang = "en-US";
     speechSynthesis.speak( synthes );

      currentWord = words[next].w;
      w.innerHTML = currentWord;
      m.innerHTML = words[next].m;
      c.innerHTML = words[next].c;
      currentLocation = 0;

      for (var x = 0; x < listLength; x++) {  //予知色
          if(list[x].value == currentWord[currentLocation]){
           document.getElementById('key_' + list[x].key).setAttribute('fill','#98FB98');
            if(list[x].shift == true){
              shiftColor('#9ACD32');
            }else{
              shiftColor('none');
            }
         }
      }
    }

6. 押されたキーが正しいか判定する

ゲームの中心は keydown(キーが押されたとき)のイベントです。押されたキー番号 e.keyCode と Shift の有無から、対応表で文字を調べ、お題の今の位置の文字 currentWord[currentLocation] と同じかどうかを比べます。

english.html(331〜356行目) JavaScript
//*キー判定
    window.addEventListener('keydown', function(e) {
      if (!isStarted) {
        return;
      }

      for (var j = 0; j < listLength; j++) {
          if(list[j].value == currentWord[currentLocation]){
          currentShift = list[j].shift;
         }
      }

      wordsShift=false;
      shift = e.shiftKey;

      for (var j = 0; j < listLength; j++) {
          if(list[j].key == e.keyCode && list[j].shift == shift){
          listValue = list[j].value;
          wordsShift=list[j].shift;
         }
      }

      if (listValue == currentWord[currentLocation]){
        currentLocation++;
        keyLocation++
  • 正解なら:currentLocation を1つ進め、打ち終わった文字を * に置き換えて表示します。
  • まちがいなら:miss を1つ増やします。ただし、Shift キーそのものを押しただけのときはミスにしません。
english.html(377〜383行目) JavaScript

      var placeholder = '';
      for (var i = 0; i < currentLocation; i++) {
        placeholder += '*';
      }
        
      w.innerHTML = placeholder + currentWord.substring(currentLocation);

7. 最後のお題まで打ったら結果を出す

お題を最後まで打ち終わり、さらにそれが最後のお題(num-1 == next)なら、タイマーを止めて正確性を表示します。

english.html(390〜409行目) JavaScript
       scoreLabel.innerHTML = score;
      if (currentLocation === currentWord.length) {
       if (num-1 == next){
         clearTimeout(timerId);
         timer = document.getElementById('timer');
         console.dir(timer.innerHTML);  //データベースへ登録値
         next = 0;
         m.innerHTML = '';
         c.innerHTML = '';
         w.innerHTML = '正確性: ' + floatFormat(score / (score + miss) * 100, 1) + '%<br>Press any key to start<br>もう一度チャレンジする';
         score = 0;
         miss = 0;
         keyLocation = 0;
         shiftColor('none');
         setTimeout(function(){isStarted = false;}, 1000);
       }else{
        next = next + 1;
        setTarget(next);
        
       }

正確性は「正しく打った数 ÷(正しく打った数+ミスの数)× 100」で計算しています。書き直す前は計算式がまちがっていて、ほぼ常に 100% 近くになっていたので修正しました。あわせて、経過時間のミリ秒が2桁しか表示されない不具合も直しています。

8. タブで言語を切り替える(index.php)

このページでは、ゲーム本体を <iframe> で埋め込んでいます。タブのボタンに data-src でファイル名を持たせておき、押されたら iframe の src を書き換えるだけで、別の言語版に切り替わります。

index.php(11〜16行目) HTML
<div class="typing-tabs" role="tablist">
  <button type="button" class="is-active" data-src="english.html">英語</button>
  <button type="button" data-src="jp-kana.html">日本語かな</button>
  <button type="button" data-src="korean.html">ハングル</button>
  <button type="button" data-src="tw.html">注音(繁体字)</button>
</div>
index.php(28〜37行目) JavaScript
<script>
// タブを押したら iframe の中身(ゲームの言語)を切り替える
$(function(){
    $('.typing-tabs button').on('click', function(){
        $('.typing-tabs button').removeClass('is-active');
        $(this).addClass('is-active');
        $('.demo-frame').attr('src', $(this).data('src'));
    });
});
</script>

e.keyCode は古い書き方で、今は e.key(押された文字)や e.code(キーの位置)を使うのがおすすめです。ただ、かな入力やハングルのように「同じキーで別の文字」になるゲームでは、キーの位置で判定する考え方そのものは今でも役に立ちます。

words のお題を自分の好きな英単語に変えてみましょう。対応表にない文字(たとえば数字の後ろの記号)を入れると打てなくなるので、そのときは alphabet.js を確認してみてください。

↑