JavaScriptを使って、簡単なクイズコンテンツを作ってみます。
サンプルコード
まずは各画面のコーディングを行います。
今回は以下の4画面を使用します。
- 扉画面
- 問題画面
- 回答・解説画面
- 結果画面
まずは扉画面です。
jsで処理を行う要素にはjs-XXX の形でclassをつけています。
<div class="p-quiz js-quiz-content"> <h1 class="p-quiz-ttl">クイズコンテンツサンプル</h1> <div class="p-quiz-next"> <button class="c-btn js-quiz-start">開始</button> </div> </div>
問題画面です。
<div class="p-quiz js-quiz-content"> <h1 class="p-quiz-ttl">2019年8月8日にデビューしたにじさんじメンバーのユニット名はなに?</h1> <ol class="p-quiz-choices"> <li class="p-quiz-choices__item"> <button class="c-btn js-quiz-choice" data-quiz_choice="1">SMC組</button> </li> <li class="p-quiz-choices__item"> <button class="c-btn js-quiz-choice" data-quiz_choice="2">ぶるーず</button> </li> <li class="p-quiz-choices__item"> <button class="c-btn js-quiz-choice" data-quiz_choice="3">ぽさんけ</button> </li> <li class="p-quiz-choices__item"> <button class="c-btn js-quiz-choice" data-quiz_choice="4">チューリップ組</button> </li> </ol> </div>
選択肢の何番目を選択したかはdata-quiz_choiceを使って管理します。
問題画面のデモページ
回答・解説画面です。
<div class="p-quiz js-quiz-content"> <h1 class="p-quiz-ttl">2019年8月8日にデビューしたにじさんじメンバーのユニット名はなに?</h1> <p class="p-quiz-result">正解</p> <p class="p-quiz-commentary">ぽさんけは天宮こころ、エリー・コニファー、ラトナ・プティの3人で構成されるユニット。</p> <div class="p-quiz-next"> <button class="c-btn js-quiz-next">次の問題</button> </div> </div>
結果画面です。
<div class="p-quiz js-quiz-content"> <h1 class="p-quiz-ttl">結果は5問中3問正解でした</h1> <p class="p-quiz-commentary">まあまあです。</p> <div class="p-quiz-next"> <button class="c-btn js-quiz-top">トップに戻る</button> </div> </div>
各画面にCSSを当てて、各画面の準備は完了です。
.c-btn { -webkit-appearance: none; appearance: none; display: inline-flex; align-items: center; justify-content: center; width: 100%; height: 40px; padding: 0; border: none; outline: none; color: white; font-size: 16px; text-decoration: none; background: gray; cursor: pointer; } .p-quiz { width: 600px; margin: 50px auto; text-align: center; } .p-quiz-ttl { margin: 0 0 20px; font-size: 24px; font-weight: normal; } .p-quiz-next { width: 300px; margin: auto; } .p-quiz-choices { display: inline-flex; flex-wrap: wrap; width: 100%; margin: 0; padding-left: 0; } .p-quiz-choices__item { width: calc(50% - 10px); list-style: none; margin: 5px; }
次にJavaScriptでの処理を追加してみます。
先ほど作成した各画面をjsから出力する構造にするため、HTML上は以下の要素のみにします。
<div class="p-quiz js-quiz-content"></div>
クイズの内容を管理するquiz.jsonを作成します。
quizの値がクイズの内容、rankの値が結果画面で使用する内容になります。
{ "quiz": [ { "q": "2019年8月8日にデビューしたにじさんじメンバーのユニット名はなに?", "a": [ "SMC組", "ぶるーず", "ぽさんけ", "チューリップ組" ], "correct": 3, "commentary": "ぽさんけは天宮こころ、エリー・コニファー、ラトナ・プティの3人で構成されるユニット。" }, ~略~ { "q": "レイン・パターソンの誕生日はいつ?", "a": [ "10月27日", "9月6日", "2月21日", "1月13日" ], "correct": 4, "commentary": "10月27日はアクシア・クローネ、9月6日はローレン・イロアス、2月21日はオリバー・エバンスの誕生日。" } ], "rank": [ { "count": 5, "comment": "すごいです。" }, { "count": 3, "comment": "まあまあです。" }, { "count": 1, "comment": "そんなにです。" }, { "count": 0, "comment": "だめです。" } ] }
JavaScriptでquiz.jsonを読み込んで、画面を出力したりイベントを設定する処理を追加します。
let quizData = {}; let currentQuizNo = 0; let correctCount = 0; // 問題データの取得 get_quiz_data(); // トップ画面の生成 generate_top_content(); // 問題開始のイベント設定 register_start_event(); /** * 問題のデータを取得する * */ function get_quiz_data() { let xhr = new XMLHttpRequest(); xhr.onload = function() { quizData = xhr.response; } xhr.open('GET', 'quiz.json'); xhr.responseType = "json"; xhr.send(); } /** * 問題開始のイベントを設定する * */ function register_start_event() { document.querySelector('.js-quiz-start').addEventListener('click', function() { // 問題画面の生成 generate_quiz_content(); // 問題の選択肢を選択したときのイベント設定 register_choice_event(); }, false); } /** * 問題の選択肢を選択したときのイベントを設定する * */ function register_choice_event() { for (var i = 0; i < document.querySelectorAll('.js-quiz-choice').length; i++) { document.querySelectorAll('.js-quiz-choice')[i].addEventListener('click', function(e) { // 回答・解説画面の生成 generate_answer_content(parseFloat(this.getAttribute('data-quiz_choice'))); // 未回答の問題がある場合 if(currentQuizNo + 1 < quizData['quiz'].length) { // 次の問題へ遷移するときのイベント設定 register_nextquiz_event(); // 全て回答済の場合 } else { // 結果画面へ遷移するときのイベント設定 register_result_event(); } }, false); } } /** * 次の問題へ遷移するときのイベントを設定する * */ function register_nextquiz_event() { document.querySelector('.js-quiz-next').addEventListener('click', function() { currentQuizNo++; // 問題画面の生成 generate_quiz_content(); // 問題の選択肢を選択したときのイベント設定 register_choice_event(); }, false); } /** * 結果画面へ遷移するときのイベントを設定する * */ function register_result_event() { document.querySelector('.js-quiz-result').addEventListener('click', function() { // 結果画面の生成 generate_result_content(); // トップへ遷移するときのイベント設定 register_top_event(); }, false); } /** * トップへ遷移するときのイベントを設定する * */ function register_top_event() { document.querySelector('.js-quiz-top').addEventListener('click', function() { // 値のリセット currentQuizNo = 0; correctCount = 0; // トップ画面の生成 generate_top_content(); // 問題開始のイベント設定 register_start_event(); }, false); } /** * トップ画面を生成する * */ // function generate_top_content() { var ins = '<h1 class="p-quiz-ttl">クイズコンテンツサンプル</h1>'; ins += '<div class="p-quiz-next">'; ins += '<button class="c-btn js-quiz-start">開始</button>'; ins += '</div>'; document.querySelector('.js-quiz-content').innerHTML = ins; } /** * 問題画面を生成する * */ function generate_quiz_content() { var ins = '<h1 class="p-quiz-ttl">' + quizData['quiz'][currentQuizNo]['q'] + '</h1>'; ins += '<ol class="p-quiz-choices">'; for (var i = 0; i < quizData['quiz'][currentQuizNo]['a'].length; i++) { ins += '<li class="p-quiz-choices__item">'; ins += '<button class="c-btn js-quiz-choice" data-quiz_choice="' + (i+1) + '">' + quizData['quiz'][currentQuizNo]['a'][i] + '</button>'; ins += '</li>'; } ins += '</ol>'; document.querySelector('.js-quiz-content').innerHTML = ins; } /** * 回答・解説画面を生成する * @param {number} choice - 選択した回答番号 */ function generate_answer_content(choice) { var ins = '<h1 class="p-quiz-ttl">' + quizData['quiz'][currentQuizNo]['q'] + '</h1>'; // 正解の場合 if(quizData['quiz'][currentQuizNo]['correct'] === choice) { ins += '<p class="p-quiz-result">正解</p>'; correctCount++; // 不正解の場合 } else { ins += '<p class="p-quiz-result">不正解</p>'; } ins += '<p class="p-quiz-commentary">' + quizData['quiz'][currentQuizNo]['commentary'] + '</p>'; // 未回答の問題がある場合 if(currentQuizNo + 1 < quizData['quiz'].length) { ins += '<div class="p-quiz-next">'; ins += '<button class="c-btn js-quiz-next">次の問題</button>'; ins += '</div>'; // 全て回答済の場合 } else { ins += '<div class="p-quiz-next">'; ins += '<button class="c-btn js-quiz-result">結果を見る</button>'; ins += '</div>'; } document.querySelector('.js-quiz-content').innerHTML = ins; } /** * 結果画面を生成する * */ function generate_result_content() { var ins = '<h1 class="p-quiz-ttl">結果は' + (currentQuizNo+1) + '問中' + correctCount + '問正解でした</h1>'; for (var i = 0; i < quizData['rank'].length; i++) { if(correctCount >= quizData['rank'][i]['count']) { ins += '<p class="p-quiz-commentary">' + quizData['rank'][i]['comment'] + '</p>'; break; } } ins += '<div class="p-quiz-next">'; ins += '<button class="c-btn js-quiz-top">トップに戻る</button>'; ins += '</div>'; document.querySelector('.js-quiz-content').innerHTML = ins; }
基本的にはコメントで追記している通りで、12~24行目のget_quiz_data()でjsonデータを取得、26~102行目の関数で各イベント設定、104行目~182行目の関数で画面の生成・切り替えを行っています。
これで簡単な内容ですが、クイズのコンテンツを作成できました。
クイズコンテンツのデモページ
扉と問題・回答、結果でページを分ける
先ほどは同一URLで動的に画面を切り替えましたが、例えば結果画面をSNSでシェアさせる場合、結果に応じてOGP画像などを変えたいということがあります。
その場合は結果に応じてページが必要になるため、PHPなどを使ってページを作成するか、結果の数分のHTMLページを用意するなどの対応が必要になります。
今回はJavaScriptでコンテンツを作成しているため、扉画面と問題・回答画面、結果画面の3ページにわけて、さらに結果画面は結果の数分だけページを作成してみます。
まずは各HTMLを用意します。
扉ページは最初に作成したコーディングとほぼ同じで問題ないですが、開始ボタンだけaタグでリンクできるように変更しておきます。
<div class="p-quiz"> <h1 class="p-quiz-ttl">クイズコンテンツサンプル</h1> <div class="p-quiz-next"> <a href="quiz.html" class="c-btn">開始</a> </div> </div>
合わせてjs処理も行わなくなるので、js-XXXの形のclassも除去しています。
問題・回答ページはコンテンツが動的に変更されるので、先ほどと同じく以下のようにしておきます。
<div class="p-quiz js-quiz-content"></div>
結果ページも扉ページと同じく、トップに戻るボタンをリンクに変更とjs-XXXのclassを除去します。
<div class="p-quiz"> <h1 class="p-quiz-ttl">結果は5問中5問正解でした</h1> <p class="p-quiz-commentary">すごいです。</p> <div class="p-quiz-next"> <a href="./" class="c-btn">トップに戻る</a> </div> </div>
今回の場合結果は6パターンなので、result01.html、result02.html、result03.html、result04.html、result05.html、result06.htmlのように各画面作成しました。
次にquiz.jsonのrankの項目を変更します。
{ "quiz": [ ~略~ ], "rank": [ { "count": 5, "page": "result01.html" }, { "count": 4, "page": "result02.html" }, { "count": 3, "page": "result03.html" }, { "count": 2, "page": "result04.html" }, { "count": 1, "page": "result05.html" }, { "count": 0, "page": "result06.html" } ] }
結果に応じて遷移先を変更できるように項目を追加しました。
最後に問題・回答ページで使用するJavaScriptの処理です。
let quizData = {}; let currentQuizNo = 0; let correctCount = 0; // 問題データの取得 get_quiz_data(); /** * 問題のデータを取得する * */ function get_quiz_data() { let xhr = new XMLHttpRequest(); xhr.onload = function() { quizData = xhr.response; // 問題画面の生成 generate_quiz_content(); // 問題の選択肢を選択したときのイベント設定 register_choice_event(); } xhr.open('GET', 'quiz.json'); xhr.responseType = "json"; xhr.send(); } /** * 問題の選択肢を選択したときのイベントを設定する * */ function register_choice_event() { for (var i = 0; i < document.querySelectorAll('.js-quiz-choice').length; i++) { document.querySelectorAll('.js-quiz-choice')[i].addEventListener('click', function(e) { // 回答・解説画面の生成 generate_answer_content(parseFloat(this.getAttribute('data-quiz_choice'))); // 未回答の問題がある場合 if(currentQuizNo + 1 < quizData['quiz'].length) { // 次の問題へ遷移するときのイベント設定 register_nextquiz_event(); } }, false); } } /** * 次の問題へ遷移するときのイベントを設定する * */ function register_nextquiz_event() { document.querySelector('.js-quiz-next').addEventListener('click', function() { currentQuizNo++; // 問題画面の生成 generate_quiz_content(); // 問題の選択肢を選択したときのイベント設定 register_choice_event(); }, false); } /** * 問題画面を生成する * */ function generate_quiz_content() { var ins = '<h1 class="p-quiz-ttl">' + quizData['quiz'][currentQuizNo]['q'] + '</h1>'; ins += '<ol class="p-quiz-choices">'; for (var i = 0; i < quizData['quiz'][currentQuizNo]['a'].length; i++) { ins += '<li class="p-quiz-choices__item">'; ins += '<button class="c-btn js-quiz-choice" data-quiz_choice="' + (i+1) + '">' + quizData['quiz'][currentQuizNo]['a'][i] + '</button>'; ins += '</li>'; } ins += '</ol>'; document.querySelector('.js-quiz-content').innerHTML = ins; } /** * 回答・解説画面を生成する * @param {number} choice - 選択した回答番号 */ function generate_answer_content(choice) { var ins = '<h1 class="p-quiz-ttl">' + quizData['quiz'][currentQuizNo]['q'] + '</h1>'; // 正解の場合 if(quizData['quiz'][currentQuizNo]['correct'] === choice) { ins += '<p class="p-quiz-result">正解</p>'; correctCount++; // 不正解の場合 } else { ins += '<p class="p-quiz-result">不正解</p>'; } ins += '<p class="p-quiz-commentary">' + quizData['quiz'][currentQuizNo]['commentary'] + '</p>'; // 未回答の問題がある場合 if(currentQuizNo + 1 < quizData['quiz'].length) { ins += '<div class="p-quiz-next">'; ins += '<button class="c-btn js-quiz-next">次の問題</button>'; ins += '</div>'; // 全て回答済の場合 } else { ins += '<div class="p-quiz-next">'; for (var i = 0; i < quizData['rank'].length; i++) { if(correctCount === quizData['rank'][i]['count']) { ins += '<a href="' + quizData['rank'][i]['page'] + '" class="c-btn">結果を見る</a>'; break; } } ins += '</div>'; } document.querySelector('.js-quiz-content').innerHTML = ins; }
クイズコンテンツの画面を分けた場合のデモページ
基本的には前のコードの流用で、扉画面や結果画面で使用していた部分を削除しています。
合わせて、すべての問題に回答した後の98~103行目で正解数に応じて遷移先の調整を行っています。
コメントが承認されるまで時間がかかります。