Webアプリでクイズを作ろう ~後半:動きをつける1~

プログラミング

こんにちはるりです。

前半では各ページの見た目が完成しました。
しかし、このままではクイズの問題はおろか、正解、解説などがページに表示されていません。
というわけで、今回はJavaScriptをつかって問題など文章の表示・ボタンを押した時のページの遷移など、いわゆる”動き”を付けていきます。

仕組み

先ほど、JavaScriptで文章を表示すると書きましたが何故もともとHTMLで書いておかないのかと思ったかもしれません。
そこの仕組みから見ていきましょう。

JavaScriptで文を表示する理由

<理由1>

まず、クイズを作るとなったとき問題を用意すると思います。では、何問用意しますか?1問?3問?それくらいならHTMLで元々表示していても問題ありません。しかし、わざわざWebサイトとして用意するのですから、もっとたくさん問題を用意するのではないでしょうか。
たとえば50問クイズを作るとしましょう。もしHTMLで問題を表示しようとすると、問題1つに付き1ページ…つまり50ページHTMLファイルを用意することになります。50ページ!?

書くだけならコピペをすればいいのでは?と思うかもしれませんが、別のホームページを作りリンクを貼りたいとなったら、その都度50ページすべてにコードを書き込む必要があります。とてもめんどくさい…
そんなことにならないようにHTMLファイルは1つだけ用意し、文章だけ変えれる仕組みにするとHTMLファイルを1つ、JavaScriptファイルを1つ用意するだけでその後何か変更したいことが出来たときもすぐに対応できます。

<理由2>

僕の作ったコードの仕組みとして、元々用意してある「正解」と押したボタンに表示されている「選択肢」の文字列が一致したときに正解のページに飛び、一致しないときは不正解のページに飛ぶようにしています。
そうするとどのボタンに正解を表示してもエラーが起こりません。
この仕組みを使おうとすると、「ボタンの文字」「正解」の2つを取得、用意しておく必要があります。

???

ちょっと意味が分からないですよね。
僕も最初は「よー分からん」って感じでしたww
詳しくは後程コードと共に説明しますので、今のところは “ボタンを使うために必要” 程度に考えておいてください。

JavaScriptでコードを書いていく

クイズ出題ページ

まずはクイズ出題ページから作っていきましょう。
具体的にやることとしては、「前半で作ったページに問題を表示する」・「ボタンを押したときに正誤判定をする」の2つです。

まずはコードを見ましょう

//クイズの内容
const quiz = [
    {
        question: "アルファベットを順番に並べたとき、最初に現れる文字は?",
        answers: [
            "a",
            "b",
            "c",
            "d",
        ],
        correct: "a"
    }
];

const $button = document.getElementsByTagName("button");
const buttonLength = $button.length;
const correct = "correct.html";
const incorrect = "Incorrect.html";

let quizIndex = 0;
let countScore = 0;

//ローカルストレージから変数を呼び出す
const loadNumber = () => {
    if(sessionStorage.getItem("quizNumber")) {
        quizIndex = sessionStorage.getItem("quizNumber");
        countScore = sessionStorage.getItem("score");
    }
}
loadNumber();

//問題、ボタンの内容を変える
const quizSet = () => {
    document.getElementById("question").textContent = quiz[quizIndex].question;

    for(let i = 0; i < quiz[quizIndex].answers.length; i++) {
        $button[i].textContent = quiz[quizIndex].answers[i];
    }
}
quizSet();

//解答によってページを遷移
const check = (e) => {
    //正解の時
    if(quiz[quizIndex].correct === e.target.textContent) {
        countScore++;
        document.location.replace(correct);
    } else {        //不正解の時
        document.location.replace(incorrect);
    }
    quizIndex++;

    //ローカルストレージにquizIndexを保存
    sessionStorage.setItem("quizNumber", quizIndex);
    sessionStorage.setItem("score", countScore);
    sessionStorage.setItem("length", quiz.length);
}

let handlerIndex = 0;
while(handlerIndex < buttonLength) {
    $button[handlerIndex].addEventListener("click", (e) => {
        check(e);
    });
    handlerIndex++;
}

まずは定数を用意します。
2~13行目で定数(quiz)を作ります。
こんかい、この定数を配列として用意しているのですが、さらにその中に{}が用意されています。
実は、定数の中にさらにオブジェクトを用意することが出来るのです。
オブジェクトに関してはここで説明すると長くなってしまうのでまた別の機会に。

こうすると便利になります。
なにが便利になるかって?
それは次のコードを見てください。

const quiz = [
    {
        question: "アルファベットを順番に並べたとき、最初に現れる文字は?",
        answers: [
            "a",
            "b",
            "c",
            "d",
        ],
        correct: "a"
    }, {
        question: "ひらがなを50音順に並べたとき、6番目に現れる文字は?",
        answers: [
            "あ",
            "え",
            "か",
            "く",
        ],
        correct: "か"
    }
];

さきほどの2~13行目を拡張しました。
このコードだと問題が2つありますよね?
そうなんです。2つの問題が「quiz」という1つの定数で管理することが出来るのです。
こうすることで何個も何個も変数を定義する必要がなくなるし、for文で管理できるのが一番の利点です。

問題

1つ目のコードに戻りますが、37行目で定数quizに格納されている問題文を表示します。“document.getElementById(” “)”というのは、()内と同じHTMLのIDのドキュメント(文章)を取得します。続く“=quiz[quizIndex].question;”で定数quizのquizIndex番目のquestionの内容を先ほど取得したドキュメントに代入します。
こうすることで文章を変えれるわけですね。
ちなみに、今出てきたquizIndexというのは21行目で定義した変数で、何個目の問題を表示するか判定するのに使います。
このquizindexの数を変えれば複数の問題をquizオブジェクトに格納しても順番に表示できるわけです。

ボタンの定義

まずはボタンを用意します。
ボタンに表示する文字列ですが、オブジェクトには配列として格納しています。(5~10行目)
ボタンはクリックしたときの判定などがあるので、”document.getElementsByTagNane(“button”)”を複数回書くのはめんどくさいですし、間違える可能性があります。
なので16行目で”$button”という定数にdocument.getElementsByTagName(“button”)を代入します。
それと、ボタンは4つありますのでHTMLでIDをつけることはできません。なのでタグ(“<>”の名前)を取得し、ドキュメントを表示します。

4行書くのはめんどくさいので、for文で4回書いています。(37~39行目)

ボタンの動作

次にボタンを押した時の動作を追加しましょう。
ますは44行目の定数”check“です。これに関数を入れます。
今回は、アロー演算子を使っていきます。“= (e) =>”の()内のeはイベントリスナーと呼ばれ、あらかじめJavaScriptに用意されたイベント(「クリック」、「ページが表示される」など)を引数とします。
今回はクリックされたときにcheckが動くようにしています。

46行目、“e.target.textContent”は、イベントが発生した(e.target)テキスト(textContent)を取得しています。 ボタンがクリックされた時にここで取得した文字が、quizオブジェクトのcorrectと一致すると正解ページに遷移(48行目)、一致しなければ不正解ページの遷移(50行目)する仕組みとなっています。そして複数問題を出すとき、何問目なのかをカウントしなければならないので52行目で“quizIndex”という変数をプラス1しています。

ここまでで関数を用意しました。実際にこれを動かしているのは60~66行目です。
今のままではボタンに動作が振られていないので、ボタンを押してもなにも起こりません。
61行目からのwhile文で、ボタン4つすべてに先ほど用意した関数checkを振ります。
今回、while文を使いましたがボタンの数が確定しているのならばfor文を使っても問題は無いです。

62行目、“$button[handlerIndex].addEventListener(“click”, 関数)”でhandlerIndex番目のボタンにclick(マウスのクリック)が起こったときに後の関数が動作するようにイベントを追加しています。

数の判定

大方コードの解説が終わりましたが、最後に数の判定です。
このプログラムはボタンを押すたびに違うHTMLファイルに遷移します。各HTMLにそれぞれJavaScriptコードをリンクしてますので、毎回変数の値は初期化されていしまいます。
そうすると、毎回問題は1問目を出題したり、正解してもページが変わるごとに0にリセットされてしまったりなどうまく動きません。

そこで、JavaScript自体に変数を保存するのではなくブラウザ自体に保存をしてしまいましょう。
クイズを解いている限りブラウザは開いているのでそこに保存をすればコードで初期化されても問題ありません。

いくつか種類があり、代表的なのは「LocalStrage」「SessionStrage」「cokie」「session」があります。詳しくは触れませんが、これらの違いはデータの保持期限保存される場所が違います。
今回は保持期限がタブを閉じるまでブラウザのみに保存されるSessionStrageを使っていきます。

今回必要なのは、「出題するクイズの順番」と「正解数」の2つです。21、22行目でそれぞれ”quizIndex”,”countScore”として変数を定義しています。これらを54,55行目でセッションストレージに保存しています。
その時に保存する際の名前は分かりやすいようにしましょう。

では、保存したら何処で呼び出すのか。それは23~30行目です。こちらもアロー演算子で定数に入れています。最初の1回(1問目)の時は呼び出される必要が無いので、if文でセッションストレージ内に変数があるときだけ呼び出すようにしています。
30行目で定義した関数を実行します。

注意点

ストレージから呼び出すときは、変数の定義の後にしましょう。
もし1回目は呼び出されないからと言って定義より先に書くと、2回目以降に変数を呼び出しても0で初期化されてしまいます。

意外と陥る可能性のある罠なので注意してください。

そして56行目の“length”はquizオブジェクトの要素数をストレージに格納しています。
これは、結果画面で使いますので解説はその時にします。

ページの遷移

47,49行目の“document.location.replace()”は現在のURLを置換するメソッドです。
47行目では引数に“correct”と書いていますが、これは17行目で定数として定義しています。なので実際はcorrect.htmlに遷移しているわけです。この時、URLは‘置換’されるのでブラウザの戻るボタンは機能しません。49行目は同じように、18行目で定義した定数“incorrect”を引数にしています。

終わり

以上問題ページの解説でした。
文を書いていたら思ったより長くなってしまいましたので続きは次の記事にしたいと思います。

それではまた。

コメント