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行目で正解数に応じて遷移先の調整を行っています。
コメントが承認されるまで時間がかかります。