【Html&Cssで作る四択クイズゲーム開発記録⑥】ネイティブなJavascriptスクリプトをフル活用してHTMLタグ要素(DOM)を自動生成する
※ 当ページには【広告/PR】を含む場合があります。
2021/01/14
改良点の考察〜HTML要素を動的にスクリプトから生成する
<form>タグ
<!-- ...中略 -->
<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><li><li><li>
</div>
<div id="stage---2" class="stage stage--main stage--main--2">
<label for="stage2"><ul class="flexlist"><li><li><li><li>
</div>
<div id="stage---3" class="stage stage--main stage--main--3">
<label for="stage3"><ul class="flexlist"><li><li><li><li>
</div>
<div id="stage---reset" class="stage stage--end">
<p class="score-board"><span id="score"></p>
<label for="reset">もう一度トライ</label>
</div>
</form>
<!-- ...中略 -->
最小の「JSフレームワーク」を自作する
//👇属性付きのDOM要素をレンダリングする関数
const createElement = (tagName, attrs) => {
const _e = document.createElement(tagName);
if (attrs) {
for (let attr in attrs) { _e[attr] = attrs[attr]; }
}
return _e;
};
//👇使用例
document.addEventListener("DOMContentLoaded", () => {
document.getElementById('wrapper')
.appendChild(createElement('div', {
innerHTML: 'これは最初のDIVです。'
}))
.appendChild(createElement('div', {
innerHTML: 'これは2番めのDIVです。'
}))
.appendChild(createElement('div', {
innerHTML: 'これは3番めのDIVです。'
}))
.appendChild(createElement('span', {
innerHTML: 'これは3番めの中の最初のSPANです。'
}));
});
<!-- ...中略 -->
<form id="game-wrapper"></form>
<!-- ...中略 -->
document.addEventListener("DOMContentLoaded", () => {
const stageLength = 6;
const parent = document.getElementById('game-wrapper');
parent.appendChild(createElement('p', {
className: 'game-header'
})).appendChild(createElement('span', {
id: 'gamestate',
innerHTML: 'さあ4択クイズを始めましょう'
}));
parent.appendChild(createElement('input', { id: 'reset', type: 'reset' }));
parent.appendChild(createElement('input', { id: 'start', type: 'checkbox' }));
parent.appendChild(createElement('div', {
id: 'stage---op',
className: 'stage stage--op'
})).appendChild(createElement('label', {
htmlFor: 'start',
innerHTML: 'クリックしてスタート'
}));
for (let i=1; i<=stageLength; i++) {
parent.appendChild(createElement('input', { id: `stage${i}`, type: 'checkbox' }));
parent.appendChild(createElement('input', { id: `checkmark${i}`, type: 'checkbox' }));
const ul_ = parent.appendChild(createElement('div', {
id: `stage---${i}`,
className: `stage stage--main stage--main--${i}`
})).appendChild(createElement('label', {
htmlFor: `stage${i}`
})).appendChild(createElement('ul', {
className: 'flexlist'
}));
for (let j=0;j<4;j++) { ul_.appendChild(document.createElement('li')); }
}
const childResetDiv = parent.appendChild(createElement('div', {
id: 'stage---reset',
className: 'stage stage--end'
}));
childResetDiv.appendChild(createElement('p', {
className: 'score-board'
})).appendChild(createElement('span', {
id: 'score'
}));
childResetDiv.appendChild(createElement('label', {
htmlFor: 'reset',
innerHTML: 'もう一度トライ'
}));
//...略
});
まとめ
付録〜改良前のソースコード(バックアップ)
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="stage4"/>
<input type="checkbox" id="stage5"/>
<input type="checkbox" id="stage6"/>
<input type="checkbox" id="checkmark1"/>
<input type="checkbox" id="checkmark2"/>
<input type="checkbox" id="checkmark3"/>
<input type="checkbox" id="checkmark4"/>
<input type="checkbox" id="checkmark5"/>
<input type="checkbox" id="checkmark6"/>
<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><li><li><li>
</div>
<div id="stage---2" class="stage stage--main stage--main--2">
<label for="stage2"><ul class="flexlist"><li><li><li><li>
</div>
<div id="stage---3" class="stage stage--main stage--main--3">
<label for="stage3"><ul class="flexlist"><li><li><li><li>
</div>
<div id="stage---4" class="stage stage--main stage--main--4">
<label for="stage4"><ul class="flexlist"><li><li><li><li>
</div>
<div id="stage---5" class="stage stage--main stage--main--5">
<label for="stage5"><ul class="flexlist"><li><li><li><li>
</div>
<div id="stage---6" class="stage stage--main stage--main--6">
<label for="stage6"><ul class="flexlist"><li><li><li><li>
</div>
<div id="stage---reset" class="stage stage--end">
<p class="score-board"><span id="score"></p>
<label for="reset">もう一度トライ</label>
</div>
</form>
<script>
(function() {
document.getElementById("start").addEventListener("change", (e) => {
document.getElementById("gamestate").textContent = '成績: ';
});
function checkGameOver() {
let isGameOver = true;
document.querySelectorAll("input[id^='stage']").forEach((checkItem) => {
isGameOver = isGameOver && checkItem.checked;
});
return isGameOver;
}
function checkSum() {
let result = 0, total = 0;
document.querySelectorAll("input[id^='checkmark']").forEach((checkItem) => {
checkItem.checked && result++;
total++;
});
document.getElementById("score").textContent = result == total ? `全問正解🎉あなたは「タコマスター」の称号を得ました!!` : `正解率は ${result}/${total} でした。`;
}
[...Array(6).keys()].map(m => {
return {stageName: `stage${m+1}`, checkbox: `checkmark${m+1}`};
}).forEach(g => {
document.getElementById(g.stageName).addEventListener("change", (e) => {
document.getElementById("gamestate").textContent += document.getElementById(g.checkbox).checked ? '⭕ ' : '❌ ';
checkGameOver() && checkSum();
});
});
document.getElementById("reset").addEventListener("click", (e) => {
document.getElementById("gamestate").textContent = 'さあ4択クイズを始めましょう';
});
document.querySelectorAll("ul.flexlist > li").forEach((userItem) => {
userItem.addEventListener("click", (e) => {
const m_ = window.getComputedStyle(e.target,'::after').content.match(/(\d+)\/correct/);
m_ && (document.getElementById(`checkmark${m_[1]}`).checked = true);
});
});
}());
</script>
</body>
</html>
styles.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%;
}
$stages: (
'start' : ('op', ''),
'stage1': ('1', 'タコの足は何本?', ('5本', '8本', '10本', '足はない'), 2, blue),
'stage2': ('2', 'タコは何動物?', ('地球外生物', '緩歩動物', '実は植物', '軟体動物'), 4, rgb(231, 185, 99)),
'stage3': ('3', 'タコの水揚げ量が世界一の国?', ('中国','カナダ','モーリタニア','日本'), 1, rgb(211, 51, 203)),
'stage4': ('4', 'タコの心臓は何個?', ('1個','2個','3個','8個'), 3, rgb(68, 67, 8)),
'stage5': ('5', 'タコの弱点は?', ('イソギンチャク','直射日光','真水','ウミウシ'), 3, rgb(179, 53, 15)),
'stage6': ('6', 'タコの脳は何個?', ('3個','9個','12個','実は存在しない'), 2, rgb(79, 100, 4))
);
.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;
}
@for $i from 2 through length($stages) {
$item: nth($stages,$i);
$item: nth($item,2);
&--#{nth($item,1)} {
color: nth($item,5);
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;
}
&::after {
content: "wrong";
visibility: hidden;
display: block;
width: 0;
height: 0;
}
}
}
}
#stage {
&---op {display: flex}
@for $i from 2 through length($stages) {
$item: nth($stages,$i);
$item: nth($item,2);
&---#{nth($item,1)} { display: none; }
}
&---reset { display: none; }
}
@for $i from 1 through length($stages) {
$item: nth($stages, $i);
$item: nth($item,1);
##{$item}:checked ~ {
$item: nth($stages, $i);
$item: nth($item, 2);
$item: nth($item,1);
#stage---#{$item} {display: none}
@if $i == length($stages) {
#stage---reset {
display: flex;
opacity: 1;
animation: fadeIn 0.3s ease-in 0s forwards;
}
} @else {
$item: nth($stages, $i + 1);
$item: nth($item,2);
$item: nth($item,1);
#stage---#{$item} {
display: flex;
opacity: 1;
animation: fadeIn 0.3s ease-in 0s forwards;
$item: nth($stages, $i + 1);
$item: nth($item,2);
$item: nth($item,2);
label::before { content: '問題#{$i}:#{$item}'; }
$item: nth($stages, $i + 1);
$item: nth($item,2);
$quiz_option: nth($item,3);
$answer: nth($item,4);
@for $j from 1 through length($quiz_option) {
ul.flexlist {
li:nth-child(#{$j}) {
&::before {content: '#{nth($quiz_option,$j)}';}
@if $j == $answer {
&::after { content: "#{nth($item,1)}/correct"; }
}
}
}
}
}
}
}
}
@keyframes fadeIn {
0% {
display: none;
opacity: 0;
}
1% {
display: flex;
opacity: 0;
}
100% {
display: flex;
opacity: 1;
}
}
}
記事を書いた人
ナンデモ系エンジニア
これからの"地方格差"なきプログラミング教育とは何かを考えながら、 地方密着型プログラミング学習関連テーマの記事を不定期で独自にブログ発信しています。
カテゴリー