PR

初心者でもわかる!JavaScript足し算クイズアプリの作り方

【広告】

JavaScriptを学び始めたばかりの方でも取り組める、シンプルな足し算クイズアプリの作り方を解説します。本記事では、画面の構成からコードの書き方まで、順を追って丁寧に説明しているため、初心者の方でも理解しながら実際にアプリを作ることができます。

足し算クイズアプリを使ってみよう

  • 「スタート」ボタンを押すと問題が表示されます。
  • 答えを入力して送信します。
  • 正解するとスコアが1点増えます。
  • 「次の問題」ボタンを押すと、新しい問題が出題されます。
  • 間違えるとゲームが終了し、「もう一回」ボタンを押すと最初の画面に戻ります。

はじめに

この記事では、初心者でも取り組みやすい「足し算クイズアプリ」の作り方を丁寧に解説します。HTML、CSS、JavaScriptの基本的な使い方を順を追って説明しながら、実際に手を動かしてアプリを作ることができます。

プログラミング学習を始めたばかりの方でも理解できるよう、難しい用語や複雑な仕組みは避け、シンプルなコード例を使って解説します。

必要な準備(開発環境・ファイル構成など)

足し算クイズアプリを作るには、まず開発環境を整える必要があります。特別なソフトは必要なく、ウェブブラウザとテキストエディタがあれば始められます。おすすめはGoogle ChromeやEdgeなどのブラウザと、Visual Studio Codeのようなエディタです。

作業用のフォルダを作り、その中に以下の3つのファイルを用意します。

  • index.html:画面の骨組みを作るHTMLファイル
  • style.css:見た目を整えるCSSファイル
  • app.js:アプリの動きを作るJavaScriptファイル

これらのファイルを同じフォルダに置くことで、ブラウザでアプリを開いたときに正しく動作させることができます。

HTMLで画面の骨組みを作る

ここでは、足し算クイズアプリケーションの土台となるHTMLコードの構成要素と、それぞれの役割を解説します。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>足し算クイズ</title>
    <link rel="stylesheet" href="./style.css" />
  </head>
<body>
  <div class="wrap">
    <h1>足し算クイズ</h1>
    <button id="startBtn" class="startBtn show">スタート</button>

    <div id="quizWrap" class="quizWrap hide">
      <p id="scoreArea" class="scoreArea"></p>
      <div id="quizArea" class="quizArea">
        <div id="questionArea" class="questionArea"></div>
        <div id="formArea" class="formArea">
          <form>
            <div class="inputArea">
              <input
                type="number"
                id="inputNumber"
                class="inputNumber"
                name="inputNumber"
                autocomplete="off"
                placeholder="答えを入力"
                min="2"
                max="198"
                required />
              <button class="submitBtn">OK</button>
            </div>
          </form>
        </div>
      </div>
    </div>

    <div id="judgeArea" class="judgeArea hide">
      <p id="judgeMsg" class="judgeMsg"></p>
      <p id="answerMsg" class="answerMsg"></p>
      <button id="nextBtn" class="nextBtn">次の問題</button>
    </div>
  </div>
  <script src="./app.js"></script>
</body>
</html>

1. 基本構造と外部ファイルの読み込み

まず、ウェブページの基本的な設定を行います。

要素役割
<!DOCTYPE html>HTML5文書であることを宣言します。
<html lang="ja">この文書の言語が日本語であることを指定します。
<meta charset="UTF-8" />文字コードをUTF-8に指定し、文字化けを防ぎます。
<title>足し算クイズ</title>ブラウザのタブに表示されるページタイトルです。
<link rel="stylesheet" href="./style.css" />デザイン(CSS)ファイルを読み込みます。
<script src="./app.js"></script>アプリケーションの動作ロジック(JavaScript)ファイルを読み込みます。

2. コンテンツエリア(.wrap)

すべての画面コンテンツを格納するコンテナです。

要素役割
<h1>足し算クイズ</h1>ページのメインタイトルです。
<button id="startBtn" ...>スタート</button>アプリケーションを開始するためのボタンです。初期表示はこのボタンのみです。

3. クイズ実行画面(#quizWrap)

ゲームが開始された後に表示される問題エリアです。最初はCSSで非表示になっています。

  • <p id="scoreArea"></p>
    現在のスコアを表示します。
  • <div id="questionArea"></div>
    ランダムに生成された足し算の問題文(例: 35 + 62)が表示されます。

3.1. 答えの入力フォーム(<input type="number">の詳細)

ユーザーが回答を入力する部分です。特に<input>タグに設定されている属性は、入力の制御とスタイリングに重要です。

属性設定値役割
typenumber数値のみの入力を許可します。
idinputNumberJavaScriptから要素を特定するためのIDです。
classinputNumberCSSでこの入力欄を装飾するためのクラス名です。
nameinputNumberフォーム要素には標準で付ける属性です。今回はJavaScriptで値を取得しますが、将来的にサーバーへデータを送るときなどに必要になります。標準的な書き方として付けておきましょう。
placeholder答えを入力入力欄が空のときに表示されるヒントテキストです。
min2入力できる最小値2 に設定します。(1+1=2)
max198入力できる最大値198 に設定します。(99+99=198)
required(なし)この入力欄を必須項目とし、未入力での送信を防ぎます。

4. 判定表示画面(#judgeArea)

回答後にポップアップで表示されるフィードバックエリアです。最初はCSSで非表示になっています。

  • <p id="judgeMsg"></p>
    正解/不正解の判定結果メッセージを表示します。
  • <p id="answerMsg"></p>
    正しい計算式と答え(例: 35 + 62 = 97)を表示します。
  • <button id="nextBtn" class="nextBtn">次の問題</button>
    次の操作(次の問題へ進む、またはリトライ)を行うためのボタンです。

CSSで見た目を整える

CSS(スタイルシート)は、HTMLで作ったアプリの「骨組み」に色を付けたり、レイアウトを整えたり、「動き(アニメーション)」を設定したりする役割を持っています。

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.wrap {
  width: 100%;
  max-width: 500px;
  height: 500px;
  margin: 0 auto;
  padding: 20px;
  text-align: center;
  font-size: 18px;
}

h1 {
  margin-bottom: 30px;
  font-size: 1.6em;
}

button {
  width: 80%;
  margin: 0 auto;
  padding: 10px;
  font-size: 1.2em;
  cursor: pointer;
  background-color: #000080;
  color: #fff;
  border: none;
  border-radius: 5px;
}

button:hover {
  background-color: #000066;
}

.startBtn {
  opacity: 0;
  transition: opacity 0.2s, background-color 0.2s;
}

.startBtn.show {
  opacity: 1;
}

.startBtn.hide {
  display: none;
}

.quizWrap {
  user-select: none;
  opacity: 0;
  transition: opacity 0.3s;
}

.quizWrap.show {
  opacity: 1;
}

.quizWrap.hide {
  display: none;
}

.scoreArea {
  margin-bottom: 30px;
  font-size: 1.2em;
}

.quizArea {
  width: 100%;
  height: 250px;
  border: 1px solid #ccc;
  box-shadow: 2px 2px 4px 2px #999;
  padding: 10px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.questionArea {
  font-size: 2.5em;
  letter-spacing: 3px;
}

.formArea {
  margin: 30px auto;
  width: 90%;
}

.inputArea {
  display: flex;
  justify-content: center;
  align-items: center;
}

.inputNumber {
  width: 200px;
  height: 50px;
  padding: 6px;
  font-size: 1.2em;
  margin-right: 4px;
}

.submitBtn {
  width: 60px;
  height: 50px;
  margin: 0;
  padding: 6px 0;
}

.judgeArea {
  width: 90%;
  max-width: 450px;
  height: 280px;
  position: fixed;
  top: 0;
  left: 50%;
  z-index: 10;
  background-color: #fff;
  border: 2px solid #666;
  box-shadow: 2px 2px 4px 1px #999;
  transform: translate(-50%, -500px);
  opacity: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  transition: transform 0.3s, opacity 0.3s;
  pointer-events: none;
}

.judgeArea.show {
  transform: translate(-50%, 150px);
  opacity: 1;
  pointer-events: auto;
}

.judgeArea.hide {
  display: none;
}

.judgeMsg,
.answerMsg {
  font-size: 1.5em;
  margin-bottom: 30px;
}

このCSSコードは、大きく分けて全体のリセットと基本設定共通要素のデザインクイズ画面のレイアウト、そしてポップアップの表示・非表示の4つの部分で構成されています。

1. 全体のリセットと基本設定

すべてのブラウザで表示を統一するため、最初に基本的な余白などをリセットします。

セレクタ役割
*すべての要素の余白(marginpadding)をゼロにリセットし、サイズ計算を統一(box-sizing: border-box)します。
.wrapコンテンツ全体の幅を最大500pxに制限し、margin: 0 auto中央寄せにしています。アプリの基本となるデザインエリアです。
h1アプリのタイトル(<h1>)の下に30pxの余白を設けています。
.scoreAreaスコア表示エリアの下に30pxの余白を設けています。

2. 共通要素のデザイン(ボタンと入力欄)

アプリ内で繰り返し使うボタンやフォーム要素のデザインを設定します。

ボタン (button) のデザイン

  • すべての<button>要素に、濃い青(#000080)の背景色白い文字色を設定しています。
  • width: 80%で幅を広くとり、padding: 10pxで見やすくしています。
  • button:hoverは、マウスカーソルがボタンに乗ったとき(ホバー時)に、背景色を少し暗く(#000066)する設定です。

フォーム要素のデザイン

セレクタ役割
.inputArea答えの入力欄と「OK」ボタンを横並びにするため、display: flexで中央に配置しています。
.inputNumber入力欄の幅を200px、高さを50pxに固定し、文字が見やすいサイズに調整しています。
.submitBtn「OK」ボタンを幅60px、高さ50pxに固定し、入力欄と高さを揃えています。

3. 画面のレイアウトと問題表示

クイズ問題が表示されるメインエリアのデザインです。

  • .quizArea: 幅いっぱいに表示し、borderbox-shadow枠線と影を付けて目立たせています。
    • display: flexflex-direction: columnで、問題とフォームを縦に並べて中央に配置しています。
  • .questionArea: 問題文(例: 35 + 62)を表示する場所です。
    • font-size: 2.5emで非常に大きく表示し、letter-spacing: 3pxで文字間隔を空けて見やすくしています。
  • .formArea: フォーム全体に適切な余白を設定しています。

4. 画面の切り替えとアニメーション(動きの制御)

このCSSの最も重要なポイントは、JavaScriptと連携して画面を切り替えるための設定です。要素にクラス(.show.hide)を付け替えるだけで、「見せる/隠す」「動かす」という動作を実現しています。

画面の表示/非表示の切り替え

  • .startBtn, .quizWrap: 初期状態ではopacity: 0(透明)や display: none(非表示)に設定されています。
  • .hideクラスが付けば完全に非表示(display: none.showクラスが付けば表示(opacity: 1されます。
  • transitionプロパティにより、透明度(opacity)や背景色などが滑らかに変化するアニメーション効果を加えています。

判定ポップアップ(#judgeArea)のアニメーション

判定結果を表示するポップアップのデザインと動きを定義しています。

プロパティ役割
position: fixed画面の決まった位置に固定します。
transform初期状態ではポップアップを画面外(-500px)に移動させ、見えない位置に待機させています。
.judgeArea.showJavaScriptが.showクラスを付けた瞬間、transformの値が変わり、ポップアップが画面上部から滑り落ちるように表示されます。
transition表示・移動に0.3sかけて滑らかに動くように設定しています。
pointer-eventsnoneで非表示時はポップアップの下にある要素をクリックできる状態にし、.show時はautoでポップアップ自体を操作可能にしています。

このCSSファイルがあることで、アプリは単に表示されるだけでなく、スタートボタンを押したときや回答したときに、動きのあるユーザーフレンドリーな画面切り替えを実現しているわけです。

JavaScriptでアプリの動きを作る

JavaScript(app.js)は、アプリの動作(ロジック)を制御する心臓部です。「スタートボタンが押されたら問題を出す」「答えが合っているか確認する」といった、すべての機能を担当します。

const startBtn = document.querySelector('#startBtn');
const quizWrap = document.querySelector('#quizWrap');
const scoreArea = quizWrap.querySelector('#scoreArea');
const quizArea = quizWrap.querySelector('#quizArea');
const questionArea = quizWrap.querySelector('#questionArea');
const form = quizWrap.querySelector('form');
const input = form.querySelector('#inputNumber');
const judgeArea = document.querySelector('#judgeArea');
const judgeMsg = judgeArea.querySelector('#judgeMsg');
const answerMsg = judgeArea.querySelector('#answerMsg');
const nextBtn = judgeArea.querySelector('#nextBtn');

function slideOutAndHide(elem, time) {
  elem.classList.remove('show');
  setTimeout(() => {
    elem.classList.add('hide');
  }, time);
}

function slideInShow(elem, time) {
  elem.classList.remove('hide');
  setTimeout(() => {
    elem.classList.add('show');
  }, time);
}

let isAnswered = false;
let score = 0;
let firstNum, secondNum;

const quizSet = function () {
  firstNum = Math.floor(Math.random() * 99) + 1;
  secondNum = Math.floor(Math.random() * 99) + 1;
  questionArea.textContent = `${firstNum} + ${secondNum}`;
  scoreArea.textContent = `SCORE: ${score}`;
  input.disabled = false;
  input.focus();
  judgeMsg.textContent = '';
  answerMsg.textContent = '';
};

startBtn.addEventListener('click', () => {
  quizSet();
  slideOutAndHide(startBtn, 300);
  setTimeout(() => {
    slideInShow(quizWrap, 300);
  }, 300);
});

const checkAnswer = (answer, a, b) => {
  if (isAnswered) return;
  isAnswered = true;

  const quizCorrect = a + b;
  const isCorrect = answer === quizCorrect;
  answerMsg.textContent = `${a} + ${b} = ${quizCorrect}`;

  if (isCorrect) {
    judgeMsg.textContent = '正解';
    score++;
  } else {
    judgeMsg.textContent = '不正解';
    nextBtn.textContent = 'もう一回';
    score = 0;
  }

  input.value = '';
  input.disabled = true;
  slideInShow(judgeArea, 300);
};

form.addEventListener('submit', (e) => {
  e.preventDefault();
  let inputNumber = parseInt(form.elements.inputNumber.value, 10);
  checkAnswer(inputNumber, firstNum, secondNum);
});

nextBtn.addEventListener('click', () => {
  isAnswered = false;
  questionArea.textContent = '';
  slideOutAndHide(judgeArea, 300);

  if (judgeMsg.textContent === '正解') {
    quizSet();
  } else {
    nextBtn.textContent = '次の問題';
    slideOutAndHide(quizWrap, 300);
    setTimeout(() => {
      slideInShow(startBtn, 300);
    }, 300);
  }
});

このコードは、大きく分けて要素の取得変数の定義問題の生成イベント処理の4つの部分で構成されています。

1. HTML要素の取得

まず、JavaScriptで操作したいHTML要素(ボタン、入力欄、表示エリアなど)を、それぞれのidを使って取得します。これにより、プログラムから要素の内容を変えたり、動きを付けたりできるようになります。

// 操作するHTML要素を取得
const startBtn = document.querySelector('#startBtn');
// ... (中略) ...
const input = form.querySelector('#inputNumber');
const judgeArea = document.querySelector('#judgeArea');
// ... (中略) ...

2. 画面の表示/非表示を操作する関数

CSSで定義した.show.hideクラスを使って、画面を滑らかに切り替えるための便利関数を定義しています。

関数名役割
slideOutAndHide(elem, time)要素から.showクラスを外し、指定時間(time)後に.hideクラスを付けて要素を非表示にします。(例: スタートボタンを隠す)
slideInShow(elem, time)要素から.hideクラスを外し、指定時間後に.showクラスを付けて要素をアニメーション付きで表示します。(例: 判定ポップアップを表示する)

3. アプリケーションの状態と変数の定義

ゲームの状態(スコア、問題の数値など)を保持するための変数を定義します。

let isAnswered = false; // 現在の問題に回答済みかどうか
let score = 0;          // 現在のスコア(連勝数)
let firstNum, secondNum; // 問題に使う2つのランダムな数値

4. 問題の生成と表示 (quizSet 関数)

この関数は、新しい問題を作成し、画面に表示する役割を果たします。

const quizSet = function () {
    // 1から99までのランダムな整数を2つ生成
    firstNum = Math.floor(Math.random() * 99) + 1;
    secondNum = Math.floor(Math.random() * 99) + 1;
    
    // 問題とスコアを画面に表示
    questionArea.textContent = `${firstNum} + ${secondNum}`;
    scoreArea.textContent = `SCORE: ${score}`;
    
    // 入力欄を有効化し、フォーカスを当てる
    input.disabled = false;
    input.focus();

    // 判定テキストを消す
    judgeMsg.textContent = '';
    answerMsg.textContent = '';
};

5. イベント処理(ユーザーの操作)

ユーザーがボタンを押したり、フォームを送信したりしたときの具体的な動作を定義します。

5-1. スタートボタンが押されたとき

startBtn.addEventListener('click', () => {
    quizSet(); // ① 新しい問題を生成・表示
    slideOutAndHide(startBtn, 300); // ② スタートボタンを隠す
    
    setTimeout(() => {
        slideInShow(quizWrap, 300); // ③ クイズ画面を表示する
    }, 300);
});

5-2. 回答が送信されたとき (checkAnswer 関数)

ユーザーが答えを入力し「OK」ボタンを押すと、この関数が実行され、正誤判定を行います。

const checkAnswer = (answer, a, b) => {
    if (isAnswered) return; // 二重回答を防ぐ
    isAnswered = true;

    const quizCorrect = a + b; // 正しい答えを計算
    const isCorrect = answer === quizCorrect; // 回答と正解を比較

    answerMsg.textContent = `${a} + ${b} = ${quizCorrect}`;

    if (isCorrect) {
        judgeMsg.textContent = '正解';
        score++; // 正解ならスコアを1増やす
    } else {
        judgeMsg.textContent = '不正解';
        nextBtn.textContent = 'もう一回'; // ボタンのテキストを変更
        score = 0; // 不正解ならスコアをリセット
    }

    input.disabled = true; // 回答後は入力欄を無効化
    slideInShow(judgeArea, 300); // 判定ポップアップを表示
};

5-3. 次へボタンが押されたとき (nextBtn の処理)

判定ポップアップの「次の問題」または「もう一回」ボタンが押されたときの処理です。

nextBtn.addEventListener('click', () => {
    isAnswered = false;
    slideOutAndHide(judgeArea, 300); // 判定ポップアップを隠す

    if (judgeMsg.textContent === '正解') {
        // 正解の場合:
        quizSet(); // 次の問題を生成する
    } else {
        // 不正解の場合:
        nextBtn.textContent = '次の問題'; // ボタンのテキストを戻す
        slideOutAndHide(quizWrap, 300); // クイズ画面を隠す
        
        setTimeout(() => {
            slideInShow(startBtn, 300); // スタート画面(スタートボタン)に戻る
        }, 300);
    }
});

このJavaScriptコードにより、アプリは「問題を出す」→「判定する」→「次のステップへ誘導する」という一連のクイズの流れをスムーズに実行しています。

タイトルとURLをコピーしました