アプリについて
JavaScriptで作成した狩猟免許試験【わな猟】に出題される狩猟鳥獣(捕っていい動物)と非狩猟鳥獣(捕ってはいけない動物)16種類を「捕れる」「捕れない」の2択式で解答するだけのアプリです。
ブラウザで気軽にできます。狩猟免許試験前の息抜きにどうぞ。
実行イメージ
全問終了時のイメージ(正解と不正解がわかるようになっている)
プログラム
HTML部分
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,user-scalable=yes">
<link rel="stylesheet" href="style.css">
<script src="main.js" type="text/javascript"></script>
<title>狩猟鳥獣クイズ【わな猟】</title>
</head>
<body>
<header>
<h1>狩猟鳥獣クイズ【わな猟】</h1>
</header>
<div id="contents"></div>
<footer id="footer">
<small>
<p>© <a href="https://torisky.com/" target="_blank">torisky.com</a></p>
</small>
</footer>
</body>
</html>
JavaScript部分
main.js
/*
* 狩猟鳥獣クイズ【わな猟】
* わな猟狩猟免許試験に出題される狩猟鳥獣と非狩猟鳥獣16種類について
* 「捕れる」「捕れない」で解答します
*/
// 出題される問題データ
let mondai = [ // kotae 1: 捕れる 2: 捕れない
{ text: "タヌキ", kotae : 1 }, // 狩猟鳥獣(捕れる)
{ text: "キツネ", kotae : 1 },
{ text: "テン", kotae : 1 },
{ text: "イタチ(オス)", kotae : 1 },
{ text: "ニホンジカ", kotae : 1 },
{ text: "ミンク", kotae : 1 },
{ text: "アライグマ", kotae : 1 },
{ text: "ハクビシン", kotae : 1 },
{ text: "アナグマ", kotae : 1 },
{ text: "モモンガ", kotae : 2 }, // 狩猟鳥獣と誤認されやすい鳥獣(捕れない)
{ text: "オコジョ", kotae : 2 },
{ text: "カモシカ", kotae : 2 },
{ text: "イタチ(メス)", kotae : 2 },
{ text: "二ホンリス", kotae : 2 },
{ text: "ムササビ", kotae : 2 },
{ text: "ニホンザル", kotae : 2 },
];
let count; // 出題番号(0~)
let contents; // コンテンツ表示箇所(問題と解答ボタン等)
let footer; // フッター表示箇所
/*
* pythonやC#のString.format的なメソッドをJavaScriptで実現する
*
* 参考:https://www.it-swarm-ja.tech/ja/javascript/printfstringformat%E3%81%A8%E5%90%8C%E7%AD%89%E3%81%AEjavascript/957771974/
*
*/
String.prototype.format = function(){
let formatted = this;
for(let arg in arguments){
formatted = formatted.replace("{" + arg + "}", arguments[arg]);
}
return formatted;
};
/*
* 初期化処理(リトライ時も利用)
*/
function init(){
contents.innerHTML = ""; // コンテンツクリア
count = 0; // 出題カウント(終了時チェック用)
// 問題データシャッフル
suffleMondai();
// 問題出題
nextMondai();
}
/*
* 問題の出題順をシャッフル
*/
function suffleMondai(){
for(let i=0; i<mondai.length; i++){
let r = Math.floor(Math.random() * mondai.length);
let work = mondai[i];
mondai[i] = mondai[r];
mondai[r] = work;
}
}
/*
* 次の問題を表示
*/
function nextMondai(){
// 終了チェック
if(count >= mondai.length){
checkKaitou();
return;
}
// 問題表示用pタグ生成
let text = document.createElement("p");
text.id = "mondai{0}".format(count);
text.innerHTML = "{0}".format(mondai[count].text); // 問題表示
// 解答表示用ラジオボタン生成
let kaitou = document.createElement("p");
// 解答表示用pタグ内にラジオボタンを挿入
kaitouText = "<label for='yes{0}'><input type='radio' id='yes{1}' name='kaitou{2}'>捕れる</label><label for='no{3}'><input type='radio' id='no{4}' name='kaitou{5}'>捕れない</label>".format(count, count, count, count, count, count);
console.log(kaitouText);
kaitou.innerHTML = kaitouText;
// コンテンツ部分にエレメント追加
contents.appendChild(text);
contents.appendChild(kaitou);
// ラジオボタンイベント設定
let yes = document.getElementById("yes{0}".format(count));
let no = document.getElementById("no{0}".format(count));
yes.addEventListener("click", nextMondai);
no.addEventListener("click", nextMondai);
yes.addEventListener("click", clickOnce); // 2度と押せなくする
no.addEventListener("click", clickOnce); // 2度と押せなくする
// 一番下が常に見えるようにスクロールする
footer.scrollIntoView(false);
// 出題数カウント
count++;
}
/*
* ラジオボタンは2度と押せなくする
*/
function clickOnce(){
// ラジオボタンの2つ組を取得
let targets = document.getElementsByName(this.name);
console.log(targets);
// ラジオボタンを押せなくする
for(let i in targets){
targets[i].disabled = "disabled"; // イメージ: <input type="radio" name="***" disabled>
}
}
/*
* リトライボタンの生成
*/
function createRetry(){
let retryButton = document.createElement("button");
retryButton.textContent = "もう一度";
retryButton.setAttribute("class", "retry-button"); // .retry-buttonクラスを設定
retryButton.addEventListener("click", init); // クリックすると初期化
contents.appendChild(retryButton); // コンテンツに部分に追加
// 最初の問題表示部分までスクロールする
contents.scrollIntoView(true);
}
/*
* 解答チェック
*/
function checkKaitou(){
// 正解問題リスト、不正解問題リスト、不正解した問題で選択したラジオボタンラベル
let ok = [], ng = [], label = [];
// 終了アラート
alert("全問終了");
// 正解は〇、不正解は×と正しい選択肢を示す
for(let i in mondai){
let mondaiNo = document.getElementById("mondai{0}".format(i)); // 問題表示部分
let kaitou = document.getElementsByName("kaitou{0}".format(i)); // 解答ラジオボタン
// 正解・不正解をチェック
let checked = 0;
if(kaitou[0].checked) checked = 1; // 「捕れる」にチェックした
else if(kaitou[1].checked) checked = 2; // 「捕れない」にチェックした
if(mondai[i].kotae === checked){
ok.push(mondaiNo); // 正解した問題をokオブジェクトに追加
}
else{
ng.push(mondaiNo); // 不正解の問題をngオブジェクトに追加
// 不正解のラベル部分をlabelオブジェクトに追加
if(checked == 1) label.push(kaitou[1]);
else label.push(kaitou[0]);
}
}
// 正解の表示
console.log("正解数 = " + ok.length);
for(let p of ok){
p.innerHTML = "{0}<span class='ok'>〇</span>".format(p.textContent); // 〇をつける
}
// 不正解の表示
console.log("不正解数 = " + ng.length);
for(let i in ng){
ng[i].innerHTML = "{0}<span class='ng'>×</span>".format(ng[i].textContent); // ×をつける
// 正解の選択肢に赤枠をつける
label[i].labels[0].style.borderRadius = "20px";
label[i].labels[0].style.border = "solid 2px red";
}
// リトライボタンの生成
createRetry();
}
/*
* 起動時の処理
*/
window.addEventListener("load", function(){
console.log("問題データ数 = {0}".format(mondai.length));
// DOM要素取得(コンテンツ部分、フッター部分)
contents = document.getElementById("contents");
footer = document.getElementById("footer");
// 初期化処理
init();
});
CSS部分
style.css
/* style.css */
@charset "utf-8";
*{
margin: 0;
padding: 0;
}
body{
font-family: Helvetica, 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', '游ゴシック', meiryo, sans-serif;
}
/* ヘッダ */
header{
background-color: orange;
color: black;
}
header h1{
font-size: 24pt;
padding: 20px 3em;
line-height: 1.7em;
letter-spacing: .2em;
text-align: center;
}
/* コンテンツ */
div#contents{
overflow: auto;
background-color: white;
width: 800px;
margin: 1em auto;
padding: 0;
}
div#contents p{
font-size: 18pt;
padding: 0.5em;
line-height: 1.7em;
letter-spacing: .2em;
text-align: justify;
color: black;
}
div#contents label{ /* ラジオボタンの文字 */
padding: 0.5em;
font-size: 16pt;
transition: all 0.5s ease 0s;
}
div#contents label:active{
font-weight: 900; /* */
transform: translateY(-0.18em); /* 上方向に少し移動 */
}
/* フッタ */
footer{
overflow-y: scroll; /* JS: scrollIntoView()を利用するため */
margin-top: 1em;
text-align: center;
padding: 20px 40px;
font-size: 14pt;
background-color: black;
color: yellowgreen;
}
/* 汎用タグ */
small{
font-size: 14pt;
}
a{
color: yellowgreen;
text-decoration: none;
}
a:hover{
color: white;
}
/* リトライボタン部分 */
button{
-webkit-appearance: none; /* iOS対策 */
font-size: 16pt;
}
.retry-button{
width: 100%;
margin-top: 1em;
padding: 10px;
text-align: center;
box-shadow: 3px 3px 7px #ddd; /* ボタンの影 */
margin-bottom: 0.5em;
transition: all 0.3s ease 0s; /* イベント毎の表示を0.3秒後にする*/
}
.retry-button:hover{ /* マウスが乗ったとき */
box-shadow: 3px 3px 7px #555; /* 少し影を濃く */
transform: translateY(-0.18em); /* 上方向に少し移動 */
}
/* 正解の〇 */
.ok{
padding-left: 1em;
font-weight: 900;
color: royalblue;
}
/* 不正解の× */
.ng{
padding-left: 1em;
font-weight: 900;
color: tomato;
}
/* メディアクエリ設定 */
@media screen and (min-width:376px) and (max-width:960px) {
/* タブレット用のcssを記述 */
header h1{
padding: 20px;
font-size: 16pt;
}
div#contents{
width: auto;
margin: 10px 10px;
padding: 1em;
font-size: 14pt;
}
footer{
padding: 20px;
}
}
@media screen and (max-width:376px) {
/* スマホ用のcssを記述 */
header h1{
padding: 20px;
font-size: 14pt;
}
div#contents{
width: auto;
margin: 0 0;
padding: 5px 10px;
font-size: 12pt;
}
footer{
padding: 20px;
}
small{
font-size: 11pt;
}
}
コメント