4択クイズをつくる
2024年11月6日
みなさんこんにちは。
ケミストのWeb担当みやのです。
「Web」記事では、これまでに得たWebに関する知識を記録として残していきたいと思います。
今回は、以下のサイトを丸パクリ参考にして4択クイズを作ってみました。
参考:BRISK|JavaScriptでランダム出題の4択クイズを作ろう【配列から質問と回答を取得】
さっそく挑戦してみてください。全部で5問です。
みなさんは何問正解できましたか?
今回は「用法」をテーマにしてみました。いい問題があったらぜひ教えてください。
つくってみよう
「配列の中にある問題文と解答を取得し、HTMLの中身を動的に切り替える」だそうです。む、難しそう...
HTMLは以下の通りです。
<div id="wrapper">
<main class="quiz">
<div class="inner-block">
<div class="quiz-content">
<div class="finish">
<div class="score-wrap"> <span class="score">0</span> <span class="ja">点</span> <span class="full">/100点</span> </div>
<a class="goback-button">最初からやり直す</a> </div>
<div class="quiz-question-number"></div>
<h2 class="quiz-question"></h2>
<ul class="quiz-answer">
<li>
<label class="quiz-button button01">
<input name="radio" type="radio" value="">
<span class="quiz-text01"></span> </label>
</li>
<li>
<label class="quiz-button button02">
<input name="radio" type="radio" value="">
<span class="quiz-text02"></span> </label>
</li>
<li>
<label class="quiz-button button03">
<input name="radio" type="radio" value="">
<span class="quiz-text03"></span> </label>
</li>
<li>
<label class="quiz-button button04">
<input name="radio" type="radio" value="">
<span class="quiz-text04"></span> </label>
</li>
</ul>
</div>
</div>
</main>
</div>
finishが結果表示欄
quiz-question-numberが問題番号
quiz-questionが問題文
quiz-answerの中の4つの<li>が選択肢のようです。
CSSもほぼ丸パクリしてしまいましたが、画面幅1200px以上の時とスマホ横向き時に選択肢が2×2に並ぶようにしたのと、タッチデバイス時の選択肢のhoverは無効にして、is-checked付与時に背景色を変更させるようにしました。
あと、問題文の一部にピンクの線を引いてみました。
@media only screen and (min-width: 1200px) {
.quiz-answer {
grid-template-columns: repeat(2, 1fr);
gap: 20px;
padding: 0 10px 60px;
}
}
@media only screen and (max-width: 1199px) {
.quiz-answer {
grid-template-columns: repeat(1, 1fr);
padding: 0;
gap: 8px;
}
}
@media screen and (orientation:landscape) and (max-width: 767px) {
.quiz-answer {
grid-template-columns: repeat(2, 1fr);
gap: 20px;
padding: 0 10px 60px;
}
}
@media (hover: hover) {
.quiz-answer li label:hover {
background-color: rgba(50, 189, 235, 0.2);
}
}
@media (hover: none) {
.quiz-answer li label.is-checked {
background-color: rgba(50, 189, 235, 0.2);
}
}
.highlight {
text-decoration: underline;
text-decoration-thickness: 0.5em;
text-decoration-color: rgba(255, 204, 255, 0.8);
text-underline-offset: -0.2em;
text-decoration-skip-ink: none;
}
スクリプト
さあ、ここからがハイライトだ本番です。
参考元のサイトで超絶わかりやすく説明してくれているのですが、じゃあこれを自分で組んでみてと言われても全くできる気がしないですね...
(function ($) {
'use strict';
let $questionTotalNum = 5;
const prefecturalCapital = [{
id: "01",
question: '服用後、次の服用まで<span class="highlight">4時間</span>あける必要があるのは',
answer01: "アマージ",
answer02: "ゾーミッグ",
answer03: "レルパックス",
answer04: "マクサルト",
}, {
id: "02",
question: '服用後、<span class="highlight">60分間</span>横になってはならないのは',
answer01: "ボンビバ",
answer02: "リカルボン",
answer03: "ベネット",
answer04: "ボナロン",
}, {
id: "03",
question: '<span class="highlight">空腹時</span>に投与する必要があるのは',
answer01: "ビラノア",
answer02: "デザレックス",
answer03: "アレグラ",
answer04: "ルパフィン",
}, {
id: "04",
question: '<span class="highlight">1日1回夕食後</span>と定められているのは',
answer01: "レクサプロ",
answer02: "ジェイゾロフト",
answer03: "イフェクサー",
answer04: "サインバルタ",
}, {
id: "05",
question: '朝食前or朝食後<span class="highlight">でなくてもよい</span>のは',
answer01: "フォシーガ",
answer02: "スーグラ",
answer03: "カナグル",
answer04: "ジャディアンス",
},];
function shuffleQuiz(array) {
for (let i = (array.length - 1); 0 < i; i--) {
let random = Math.floor(Math.random() * (i + 1));
let selected = array[i];
array[i] = array[random];
array[random] = selected;
}
return array;
}
let quizId = ["01", "02", "03", "04", "05"];
shuffleQuiz(quizId);
let $currentNum = 0;
let $pointPerCorrect = 20;
let questionObject = (function () {
let Obj = function ($target) {
this.$questionNumber = $target.find('.quiz-question-number');
this.$questionName = $target.find('.quiz-question');
this.$questionButton = $target.find('.quiz-button');
this.$button01 = $target.find('.button01');
this.$button02 = $target.find('.button02');
this.$button03 = $target.find('.button03');
this.$button04 = $target.find('.button04');
this.$answer01 = $target.find('.quiz-text01');
this.$answer02 = $target.find('.quiz-text02');
this.$answer03 = $target.find('.quiz-text03');
this.$answer04 = $target.find('.quiz-text04');
this.$score = $target.find('.score-wrap .score');
this.init();
};
Obj.prototype = {
init: function () {
this.event();
},
event: function () {
let _this = this;
let score = 0;
$(window).on('load', function () {
_this.loadQuestion();
});
this.$questionButton.on("click", function () {
if ($(this).hasClass('button01')) {
$(this).parents('.quiz-answer').addClass('is-correct');
score += $pointPerCorrect;
confetti();
} else {
$(this).parents('.quiz-answer').addClass('is-incorrect');
}
$(this).addClass('is-checked');
if ($currentNum + 1 === $questionTotalNum) {
setTimeout(function () {
$('.finish').addClass('is-show');
$('.score-wrap .score').text(score);
}, 1000);
if (score === 100) {
fireworks();
}
} else {
setTimeout(function () {
_this.resetChoices();
setTimeout(function () {
_this.nextQuestion();
}, 200);
}, 1000);
}
return false;
});
$('.goback-button').on('click', function () {
score = 0;
$currentNum = 0;
shuffleQuiz(quizId);
_this.resetChoices();
setTimeout(function () {
_this.loadQuestion();
}, 200);
$('.finish').removeClass('is-show');
});
},
loadQuestion: function () {
let value = quizId[$currentNum];
let nextQuestion = this.searchQuestion(value);
this.changeQuestion(nextQuestion);
this.shuffleAnswer($('.quiz-answer'));
},
nextQuestion: function () {
$currentNum++;
this.loadQuestion();
},
resetChoices: function () {
this.$questionButton.removeClass('is-checked');
$('.quiz-answer').removeClass('is-correct is-incorrect');
},
searchQuestion: function (questionId) {
return prefecturalCapital.find(item => item.id === questionId);
},
changeQuestion: function (nextQuestion) {
let _this = this;
_this.$questionName.html(nextQuestion.question + '次のうちどれか。');
let numPlusOne = $currentNum + 1;
_this.$questionNumber.text('問題' + numPlusOne);
_this.$answer01.text(nextQuestion.answer01);
_this.$answer02.text(nextQuestion.answer02);
_this.$answer03.text(nextQuestion.answer03);
_this.$answer04.text(nextQuestion.answer04);
},
shuffleAnswer: function (container) {
let content = container.find("> *");
let total = content.length;
content.each(function () {
content.eq(Math.floor(Math.random() * total)).prependTo(container);
});
},
};
return Obj;
})();
let quiz = $('.quiz');
if (quiz[0]) {
let queInstance = new questionObject(quiz);
}
})(jQuery);
「answer01」に正解の選択肢を入れる感じですね。
問題文や選択肢の順番がランダムになるようにしてあるのがすごいです。
1問正解で20点、全5問正解で100点にしました。
問題文の一部だけクラスを適用させたかったので、問題文の呼び出しはtext()ではなくhtml()にしました。
問題移行時とリセット時にsetTimeoutを設定しました。
「最初からやり直す」はページリロードではなくクイズ部分だけリセットさせるようにしました。
正解を選んだ時にconfetti、全問正解時にfireworksを仕込みました。
リセット時にfireworksを強制停止させるようにしました。
今後やりたいこと
・選択肢を5個用意して「次のうち正しいものを3つ選べ」的な問題
・問題を無数に用意して、その中からランダムで5問出題させたい
・次の問題に進む前に解説を挿入したい
このクイズ企画を思いついた時は「これで毎月のブログネタに困らなくなるぞ!」と思ったのですが、問題を考えるのが普通にしんどかったです 日〇DIとかエ〇スリーのクイズ考えてる人は本当にすごいなと思いました