【Html&Cssで作る四択クイズゲーム開発記録②】擬似要素のcontentプロパティでクイズの正誤判定に使う
※ 当ページには【広告/PR】を含む場合があります。
2021/12/18

SassをベースにHTML&CSSミニゲームを不定期で開発していく企画の第2段です。
今回は四択クイズの正誤判定を行う上で、後からクイズ問題を増やして行くことを考慮したときに、より再生産性の高い手法にSassコードを改造してみましょう。
改善点の洗い出し
まず改良前の四択問題でどのように正誤判定していたかというところから見ていきます。
※ 改善前のリソース(index.htmlとstyle.scss)のフルパージョンは
以下は第一問(stage1)の4択部分のlabelブロックを抜粋したものです。
<!-- ...中略 -->
<label for="stage1">
タコの足は何本?
<ul class="flexlist">
<li>5本</li>
<li id="correct-option-1">8本</li>
<li>10本</li>
<li>足はない</li>
</ul>
</label>
<!-- ...中略 -->
4つの選択肢のうち、正解には
id="correct-option-1"
<!-- ...中略 -->
<script>
//...中略
document.getElementById("correct-option-1").addEventListener("click", (e) => {
document.getElementById("checkmark1").checked = true;
});
//...中略
</script>
<!-- ...中略 -->
というように正解の選択肢(
<li>
でも、問題が少ないうちは良いものの、後で設問を追加していく全てに
id="correct-option-*"
また、コードの管理の面からも好ましいやり方ではありません。
そこで、より
まずは設問のラベルブロックから
id="correct-option-*"
<!-- ...中略 -->
<div id="stage---1" class="stage stage--main stage--main--1">
<label for="stage1">
タコの足は何本?
<ul class="flexlist">
<li>5本</li>
<li>8本</li>
<li>10本</li>
<li>足はない</li>
</ul>
</label>
</div>
<div id="stage---2" class="stage stage--main stage--main--2">
<label for="stage2">
タコは何動物?
<ul class="flexlist">
<li>地球外生物</li>
<li>緩歩動物</li>
<li>実は植物</li>
<li>軟体動物</li>
</ul>
</label>
</div>
<div id="stage---3" class="stage stage--main stage--main--3">
<label for="stage3">
タコの水揚げ量が世界一の国?
<ul class="flexlist">
<li>中国</li>
<li>カナダ</li>
<li>モーリタニア</li>
<li>日本</li>
</ul>
</label>
</div>
<!-- ...中略 -->
...これでは正解かどれか分からないじゃないか?と思われるかも知れませんが、正誤リストとなるデータを
style.scss
とはいえCSSスタイル用に設計されているプロパティの値は、もともとさほど自由度の高い引数を書き込めるようには出来ていません。
ではどのプロパティを使うかというと、
具体的には以下のようなコードです。
//...中略
.stage--main {
ul.flexlist {
//...中略
li {
//👇after擬似要素のcontentを全て"wrong"(間違い)で初期化
&::after {
content: "wrong";
visibility: hidden;
display: block;
width: 0;
height: 0;
}
}
}
}
#stage {
//...中略
//👇正誤判定用のリストで正解の箇所だけafter擬似要素のcontentを"<設問番号>/correct"にする
@each $item, $val in ('1':2, '2':4, '3':1) {
&---#{$item} {
ul.flexlist {
li:nth-child(#{$val}) {
&::after { content: "#{$item}/correct"; }
}
}
}
}
}
このコード内の
('1':2, '2':4, '3':1)
<li>
このリストもまだ改善の余地がありますが、とりあえず
設問1は選択肢の2番目、設問2は選択肢の4番目、設問3は選択肢の1番目
後はHTMLのスクリプト部分で、このcontentプロパティをコッソリと裏で読み取り、正解のli要素の正規表現パターンからクイズの正誤判定を一括して行うように変更することが出来ます。
<!-- ...中略 -->
<script>
//...中略
document.querySelectorAll("ul.flexlist > li").forEach((userItem) => {
userItem.addEventListener("click", (e) => {
const styles = window.getComputedStyle(e.target,'::after')
const content = styles['content'];
const m_ = content.match(/(\d+)\/correct/);
if (m_) {
console.log("checkmark" + m_[1]);
document.getElementById("checkmark" + m_[1]).checked = true;
};
});
});
</script>
ということで、SassとJavascriptとの連携に利用できる::beforeか::afterの擬似要素のcontentプロパティは、色々と使い道がありますので、この機会にじっくり理解を深めておきましょう。
まとめ
今回はCSSの擬似要素に設定できるcontentプロパティを使って、四択問題の正誤判定のデータを埋め込む方法を解説してみました。
まだ設問が3つですので、この手法がHTML&CSSミニゲームで強力に効いてくるほどではないですが、設問をガンガン増やしたいならその有り難みが出てきます。
その辺に関しては今後のリファクタリング課題を取り上げた記事で解説していく予定です。
付録〜改良前のソースコード(バックアップ)
以下は古くなったソースコード(バージョン0.2)のバックアップです。 参考までに載せておきます。
index.html
<!DOCTYPE html>
<html lang="ja">
<html>
<meta charset="utf-8"/>
<head><link rel="stylesheet" type="text/css" href="style.css"></head>
<body>
<form id="game-wrapper">
<p class="game-header"><span id="gamestate">さあ4択クイズを始めましょう</span></p>
<input type="reset" id="reset"/>
<input type="checkbox" id="start"/>
<input type="checkbox" id="stage1"/>
<input type="checkbox" id="stage2"/>
<input type="checkbox" id="stage3"/>
<input type="checkbox" id="checkmark1"/>
<input type="checkbox" id="checkmark2"/>
<input type="checkbox" id="checkmark3"/>
<div id="stage---op" class="stage stage--op">
<label for="start">クリックしてスタート</label>
</div>
<div id="stage---1" class="stage stage--main stage--main--1">
<label for="stage1">
タコの足は何本?
<ul class="flexlist">
<li>5本</li>
<li id="correct-option-1">8本</li>
<li>10本</li>
<li>足はない</li>
</ul>
</label>
</div>
<div id="stage---2" class="stage stage--main stage--main--2">
<label for="stage2">
タコは何動物?
<ul class="flexlist">
<li>地球外生物</li>
<li>緩歩動物</li>
<li>実は植物</li>
<li id="correct-option-2">軟体動物</li>
</ul>
</label>
</div>
<div id="stage---3" class="stage stage--main stage--main--3">
<label for="stage3">
タコの水揚げ量が世界一の国?
<ul class="flexlist">
<li id="correct-option-3">中国</li>
<li>カナダ</li>
<li>モーリタニア</li>
<li>日本</li>
</ul>
</label>
</div>
<div id="stage---reset" class="stage stage--end">
<p class="score-board"><span id="score"></span></p>
<label for="reset">もう一度トライ</label>
</div>
</form>
<script>
document.getElementById("start").addEventListener("change", (e) => {
document.getElementById("gamestate").textContent = '成績: ';
});
document.getElementById("correct-option-1").addEventListener("click", (e) => {
document.getElementById("checkmark1").checked = true;
});
document.getElementById("stage1").addEventListener("change", (e) => {
const r_ = document.getElementById("checkmark1").checked;
if (r_) {
document.getElementById("gamestate").textContent += '⭕ ';
} else {
document.getElementById("gamestate").textContent += '❌ ';
}
});
document.getElementById("correct-option-2").addEventListener("click", (e) => {
document.getElementById("checkmark2").checked = true;
});
document.getElementById("stage2").addEventListener("change", (e) => {
const r_ = document.getElementById("checkmark2").checked;
if (r_) {
document.getElementById("gamestate").textContent += '⭕ ';
} else {
document.getElementById("gamestate").textContent += '❌ ';
}
});
document.getElementById("correct-option-3").addEventListener("click", (e) => {
document.getElementById("checkmark3").checked = true;
});
document.getElementById("stage3").addEventListener("change", (e) => {
const r_ = document.getElementById("checkmark3").checked;
if (r_) {
document.getElementById("gamestate").textContent += '⭕ ';
} else {
document.getElementById("gamestate").textContent += '❌ ';
}
let result = 0;
document.getElementById("checkmark1").checked && result++;
document.getElementById("checkmark2").checked && result++;
document.getElementById("checkmark3").checked && result++;
document.getElementById("score").textContent = `正解率は ${result}/3 でした。`;
});
document.getElementById("reset").addEventListener("click", (e) => {
document.getElementById("gamestate").textContent = 'さあ4択クイズを始めましょう';
});
</script>
</body>
</html>
style.scss
#game-wrapper {
width: 100%;
height: 300px;
background-color: darkgray;
box-sizing: border-box;
position:relative;
input { display: none; }
.game-header {
position: absolute;
top: 0;
left: 0;
background: #220c0c;
color: #ceeece;
z-index: 1;
margin: 0;
font-size: 22px;
padding: 6px 0 6px 0;
width: 100%;
}
.stage {
position: absolute;
display: block;
width: 100%;
height: 100%;
font-size: 24px;
&--op {
display: flex;
align-items: center;
justify-content: center;
label {
display: block;
flex: 0 0 auto;
margin: 0 auto;
animation: flash 1s linear infinite;
}
}
&--main {
display: flex;
align-items: center;
justify-content: center;
label {
flex: 0 0 auto;
margin: 0 auto;
}
@each $key, $val in (1:blue, 2:rgb(231, 185, 99), 3:rgb(211, 51, 203)) {
&--#{$key} {
color: $val;
font-size: 22px;
}
}
}
&--end {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background: darkgray;
label {
flex: 0 0 auto;
display: inline-block;
font-weight: bold;
padding: 0 0 0 0;
text-decoration: none;
color: #00BCD4;
background: #ECECEC;
margin: 0 auto;
animation: flash 1s linear infinite;
}
}
@keyframes flash {
0%,100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
}
.stage--main {
ul.flexlist {
border: 1px solid #666;
display: flex;
flex-wrap: wrap;
justify-content: center;
list-style-type: none;
margin: 10px auto 2px;
padding: 0;
width: 300px;
li {
border: 1px solid #aaa;
line-height: 110%;
margin: 5px 2px 5px 2px;
padding: 8px;
text-align: center;
width: 35%;
&:hover {
color: chartreuse;
}
}
}
}
#stage {
&---op {display: flex}
@each $item in (1, 2, 3, reset) {
&---#{$item} { display: none }
}
}
$stages: (
'start' : 'op',
'stage1': '1',
'stage2': '2',
'stage3': '3'
);
@for $i from 1 through length($stages) {
##{nth(nth($stages, $i),1)}:checked ~ {
#stage---#{nth(nth($stages, $i),2)} {display: none}
@if $i == length($stages) {
#stage---reset {
display: flex;
opacity: 1;
animation: fadeIn 0.3s ease-in 0s forwards;
}
} @else {
#stage---#{nth(nth($stages, $i+1),2)} {
display: flex;
opacity: 1;
animation: fadeIn 0.3s ease-in 0s forwards;
label::before {
content: '問題#{$i}:';
}
}
}
}
}
@keyframes fadeIn {
0% {
display: none;
opacity: 0;
}
1% {
display: flex;
opacity: 0;
}
100% {
display: flex;
opacity: 1;
}
}
}