CBT,OMR형식의 퀴즈 사이트를 제작해보았습니다.
<main id="main">
<div class="quiz__wrap__cbt">
<div class="cbt__header">
<h2>2020년 1회 정보처리기능사 기출문제</h2>
</div>
<div class="cbt__conts">
<div class="cbt__quiz">
<div class="cbt good">
<div class="cbt__question"><span>1. </span>객체지향 프로그램에서 데이터를 추상화하는 단위는 ?</div>
<div class="cbt__question__img"><img src="img/gineungsaJC2023_01_01.jpg" alt="기능사"></div>
<div class="cbt__selects">
<input type="radio" id="select1">
<label for="select1"><span>클래스</span></label>
<input type="radio" id="select2">
<label for="select2"><span>메서드</span></label>
<input type="radio" id="select3">
<label for="select3"><span>상속</span></label>
<input type="radio" id="select4">
<label for="select4"><span>메시지</span></label>
</div>
<div class="cbt__desc">객체지향 언어는 ___입니다.</div>
<div class="cbt__keyword">객체지향언어</div>
</div>
<div class="cbt bad">
<div class="cbt__question"><span>2. </span>다음 빈칸을 채우시오.</div>
<div class="cbt__question__desc">객체지향 언어는 ____ 입니다.객체지향 언어는 ____ 입니다.객체지향 언어는 ____ 입니다.객체지향 언어는 ____ 입니다.객체지향 언어는 ____ 입니다.</div>
<div class="cbt__selects">
<input type="radio" id="select1">
<label for="select1"><span>클래스</span></label>
<input type="radio" id="select2">
<label for="select2"><span>메서드</span></label>
<input type="radio" id="select3">
<label for="select3"><span>상속</span></label>
<input type="radio" id="select4">
<label for="select4"><span>메시지</span></label>
</div>
<div class="cbt__desc">객체지향 언어는 ___입니다.</div>
<div class="cbt__keyword">객체지향언어</div>
</div>
</div>
</div>
<!-- cbt__conts -->
<div class="cbt__aside">
<div>
<div class="cbt__info">
<div>
<button class="cbt__time">59분 10초</button>
<span class="cbt__submit">제출하기</span>
</div>
<div>
<div class="cbt__title">수험자 : <em class="cbt__name">황인찬</em></div>
<div class="cbt__score">
<span>전체 문항 : <em class="cbt__length">0</em> 문항</span>
<span>남은 문항 : <em class="cbt__rest"">0</em> 문항</span>
</div>
</div>
</div>
<!-- cbt__info -->
<div class="cbt__omr">
<!-- <div class="omr">
<strong>1</strong>
<input type="radio" id="omr0_1">
<label for="omr0_1">
<span class="label-inner">1</span>
</label>
<input type="radio" id="omr0_2">
<label for="omr0_2">
<span class="label-inner">2</span>
</label>
<input type="radio" id="omr0_3">
<label for="omr0_3">
<span class="label-inner">3</span>
</label>
<input type="radio" id="omr0_4">
<label for="omr0_4">
<span class="label-inner">4</span>
</label>
</div> -->
</div>
<!-- cbt__omr -->
</div>
<!-- cbt__aside -->
<div>
</div>
</div>
<div class="cbt__start">
<div class="cbt_modal1">
<h2>기능사 시험 도전하기</h2>
<div class="cbt__choice">
<select class="cbt_h1" name="cbtTime" id="cbtTime">
<option class="cbt_h2" value="gineungsaJC2005_02">정보처리기능사 2005년 2회</option>
<option class="cbt_h2" value="gineungsaJC2005_04">정보처리기능사 2005년 4회</option>
<option class="cbt_h2" value="gineungsaJC2005_05">정보처리기능사 2005년 5회</option>
<option class="cbt_h2" value="gineungsaJC2006_01">정보처리기능사 2006년 1회</option>
<option class="cbt_h2" value="gineungsaJC2006_02">정보처리기능사 2006년 2회</option>
<option class="cbt_h2" value="gineungsaJC2006_03">정보처리기능사 2006년 3회</option>
<option class="cbt_h2" value="gineungsaJC2006_05">정보처리기능사 2006년 5회</option>
<option class="cbt_h2" value="gineungsaJC2007_01">정보처리기능사 2007년 1회</option>
<option class="cbt_h2" value="gineungsaJC2007_02">정보처리기능사 2007년 2회</option>
<option class="cbt_h2" value="gineungsaJC2007_05">정보처리기능사 2007년 5회</option>
<option class="cbt_h2" value="gineungsaJC2008_01">정보처리기능사 2008년 1회</option>
<option class="cbt_h2" value="gineungsaJC2008_02">정보처리기능사 2008년 2회</option>
<option class="cbt_h2" value="gineungsaJC2008_04">정보처리기능사 2008년 4회</option>
<option class="cbt_h2" value="gineungsaJC2008_05">정보처리기능사 2008년 5회</option>
<option class="cbt_h2" value="gineungsaJC2009_01">정보처리기능사 2009년 1회</option>
<option class="cbt_h2" value="gineungsaJC2009_05">정보처리기능사 2009년 5회</option>
<option class="cbt_h2" value="gineungsaJC2010_02">정보처리기능사 2010년 2회</option>
<option class="cbt_h2" value="gineungsaJC2010_05">정보처리기능사 2010년 5회</option>
<option class="cbt_h2" value="gineungsaJC2011_01">정보처리기능사 2011년 1회</option>
<option class="cbt_h2" value="gineungsaJC2011_02">정보처리기능사 2011년 2회</option>
<option class="cbt_h2" value="gineungsaJC2011_04">정보처리기능사 2011년 4회</option>
<option class="cbt_h2" value="gineungsaJC2011_05">정보처리기능사 2011년 5회</option>
</select>
<select class="cbt_h1" name="cbtTime" id="cbtTime">
<option class="cbt_h2" value="gineungsaWD2009_05">웹디자인기능사 2009년 5회</option>
<option class="cbt_h2" value="gineungsaWD2010_01">웹디자인기능사 2010년 1회</option>
<option class="cbt_h2" value="gineungsaWD2010_02">웹디자인기능사 2010년 2회</option>
<option class="cbt_h2" value="gineungsaWD2010_04">웹디자인기능사 2010년 4회</option>
<option class="cbt_h2" value="gineungsaWD2010_05">웹디자인기능사 2010년 5회</option>
<option class="cbt_h2" value="gineungsaWD2011_01">웹디자인기능사 2011년 1회</option>
<option class="cbt_h2" value="gineungsaWD2011_02">웹디자인기능사 2011년 2회</option>
<option class="cbt_h2" value="gineungsaWD2011_04">웹디자인기능사 2011년 4회</option>
<option class="cbt_h2" value="gineungsaWD2011_05">웹디자인기능사 2011년 5회</option>
<option class="cbt_h2" value="gineungsaWD2012_02">웹디자인기능사 2012년 2회</option>
<option class="cbt_h2" value="gineungsaWD2012_04">웹디자인기능사 2012년 4회</option>
<option class="cbt_h2" value="gineungsaWD2012_05">웹디자인기능사 2012년 5회</option>
<option class="cbt_h2" value="gineungsaWD2013_02">웹디자인기능사 2013년 2회</option>
<option class="cbt_h2" value="gineungsaWD2013_04">웹디자인기능사 2013년 4회</option>
<option class="cbt_h2" value="gineungsaWD2013_05">웹디자인기능사 2013년 5회</option>
<option class="cbt_h2" value="gineungsaWD2014_01">웹디자인기능사 2014년 1회</option>
<option class="cbt_h2" value="gineungsaWD2014_04">웹디자인기능사 2014년 4회</option>
<option class="cbt_h2" value="gineungsaWD2014_05">웹디자인기능사 2014년 5회</option>
<option class="cbt_h2" value="gineungsaWD2015_01">웹디자인기능사 2015년 1회</option>
<option class="cbt_h2" value="gineungsaWD2015_03">웹디자인기능사 2015년 3회</option>
<option class="cbt_h2" value="gineungsaWD2015_04">웹디자인기능사 2015년 4회</option>
<option class="cbt_h2" value="gineungsaWD2015_05">웹디자인기능사 2015년 5회</option>
<option class="cbt_h2" value="gineungsaWD2016_01">웹디자인기능사 2016년 1회</option>
<option class="cbt_h2" value="gineungsaWD2016_04">웹디자인기능사 2016년 4회</option>
</select>
</div>
<div class="button-container">
<button class="start-button">시작하기
<span class="start-button__border"></span>
<span class="start-button__border"></span>
<span class="start-button__border"></span>
<span class="start-button__border"></span>
</button>
</div>
</div>
</div>
</div>
<!-- quiz__wrap__cbt -->
</main>
<!-- //main -->
HTML
이것은 HTML 코드로 이루어진 모의고사 시험 화면입니다. 이 시험은 2020년 1회 정보처리기능사 기출문제입니다. 시험 화면은 여러 구성 요소로 이루어져 있으며, 문제와 선택지, 선택지의 설명 등을 포함합니다. 시험 화면의 왼쪽에는 수험자 정보와 시험 시간, 문제 수 등의 정보가 포함된 창이 있습니다.
const cbt = document.querySelector(".cbt")
const cbtQuiz = document.querySelector(".cbt__quiz");
const cbtOmr = document.querySelector(".cbt__omr");
const cbtSubmit = document.querySelector(".cbt__submit");
const cbtRest = document.querySelector(".cbt__rest");
const cbtLength = document.querySelector(".cbt__length");
let questionAll = []; //questionAll이라는 배열을 생성합니다.
let questionLength = 0; //전체 문제수
let questionRest = 100; //나머지
//데이터 가져오기
const dataQuestion = () => {
fetch("https://dkseho9121.github.io/web2023/javascript/quiz/json/gineungsaWD2005_02.json")
.then(res => res.json())
.then(items => {
questionAll = items.map((item, index) => {
const formattedQuestion = {
question: item.question,
number: index + 1
}
const answerChoices = [...item.incorrect_answers]; //오답 불러오기
formattedQuestion.answer = Math.round(Math.random() * answerChoices.length) + 1;
answerChoices.splice(formattedQuestion.answer - 1, 0, item.correct_answer);
//보기를 추가
answerChoices.forEach((choice, index) => {
formattedQuestion["choice" + (index+1)] = choice;
});
//문제에 대한 해설이 있으면 출력
if(item.hasOwnProperty("question_desc")){
formattedQuestion.question_desc = item.question_desc;
}
//문제에 대한 이미지가 있으면 출력
if(item.hasOwnProperty("question_img")){
formattedQuestion.questionImg = item.question_img;
}
//해설이 있으면 출력
if(item.hasOwnProperty("desc")){
formattedQuestion.desc = item.desc;
}
//console.log(formattedQuestion);
return formattedQuestion;
});
newQuestion(); //문제 만들기
//전체 문제수
questionLength = questionAll.length;
cbtLength.innerHTML = questionLength;
cbtRest.innerHTML = questionLength;
})
.catch((err) => console.log(err));
}
//문제 만들기
const updateQuiz = (index) => {
let questionnumTag = `
${cbtQuiz.length}문제중에 ${cbtQuiz.length - index}문제 남았습니다
`;
}
const newQuestion = () => {
const exam = [];
const omr = [];
questionAll.forEach((question, number) => {
exam.push(`
<div class="cbt">
<div class="cbt__question"><span>${question.number}</span>. ${question.question}</div>
<div class="cbt__question__img"></div>
<div class="cbt__selects">
<input type="radio" id="select${number}_1" name="select${number}" value="${number}_1" onclick="answerSelect2(this)">
<label for="select${number}_1"><span>${question.choice1}</span></label>
<input type="radio" id="select${number}_2" name="select${number}" value="${number}_2" onclick="answerSelect2(this)">
<label for="select${number}_2"><span>${question.choice2}</span></label>
<input type="radio" id="select${number}_3" name="select${number}" value="${number}_3" onclick="answerSelect2(this)">
<label for="select${number}_3"><span>${question.choice3}</span></label>
<input type="radio" id="select${number}_4" name="select${number}" value="${number}_4" onclick="answerSelect2(this)">
<label for="select${number}_4"><span>${question.choice4}</span></label>
</div>
<div class="cbt__desc hide">${question.desc}</div>
</div>
`);
omr.push(`
<div class="omr">
<strong>${question.number}</strong>
<input type="radio" name="omr${number}" id="omr${number}_1" value="${number}_1" onclick="answerSelect(this)">
<label for="omr${number}_1"><span class="label-inner">1</span></label>
<input type="radio" name="omr${number}" id="omr${number}_2" value="${number}_2" onclick="answerSelect(this)">
<label for="omr${number}_2"><span class="label-inner">2</span></label>
<input type="radio" name="omr${number}" id="omr${number}_3" value="${number}_3" onclick="answerSelect(this)">
<label for="omr${number}_3"><span class="label-inner">3</span></label>
<input type="radio" name="omr${number}" id="omr${number}_4" value="${number}_4" onclick="answerSelect(this)">
<label for="omr${number}_4"><span class="label-inner">4</span></label>
</div>
`)
});
cbtQuiz.innerHTML = exam.join('');
cbtOmr.innerHTML = omr.join('');
}
//정답 확인
const answerQuiz = () => {
const cbtSelects = document.querySelectorAll(".cbt__selects");
questionAll.forEach((question, number) => {
const quizSelectsWrap = cbtSelects[number];
const userSelector = `input[name=select${number}]:checked`;
const userAnswer = (quizSelectsWrap.querySelector(userSelector) || {}).value;
const numberAnswer = userAnswer ? userAnswer.slice(-1) : undefined;
if(numberAnswer == question.answer){
console.log("정답입니다.");
cbtSelects[number].parentElement.classList.add("good");
} else {
console.log("오답입니다.")
cbtSelects[number].parentElement.classList.add("bad");
//오답 일 경우 정답 표시
const label = cbtSelects[number].querySelectorAll("label");
label[question.answer-1].classList.add("corrent");
}
const quizDesc = document.querySelectorAll(".cbt__desc");
if(quizDesc[number].innerText == "undefined"){
quizDesc[number].classList.add("hide");
} else {
quizDesc[number].classList.remove("hide");
}
});
}
//보기 체크
const answerSelect2 = (elem) => {
const answer = elem.value;
const answerNum = answer.split("_");
const select = document.querySelectorAll(".cbt__omr .omr"); //전체 문항 수 100문제
const label = select[answerNum[0]].querySelectorAll("input"); //보기 4개
label[answerNum[1]-1].checked = true;
const answerInputs = document.querySelectorAll(".cbt__selects input:checked")
cbtRest.innerHTML = questionLength - answerInputs.length;
};
//보기 체크2
const answerSelect = (elem) => {
const answer = elem.value;
const answerNum = answer.split("_");
const select = document.querySelectorAll(".cbt__quiz .cbt"); //전체 문항 수 100문제
const label = select[answerNum[0]].querySelectorAll("input"); //보기 4개
label[answerNum[1]-1].checked = true;
const answerInputs = document.querySelectorAll(".cbt__selects input:checked")
cbtRest.innerHTML = questionLength - answerInputs.length;
};
cbtSubmit.addEventListener("click", answerQuiz)
dataQuestion();
//버블 버튼
const bubbleBtn = () => {
const clip = (v, min, max = Infinity) => {
if (v < min) return min;
else if (v > max) return max;
else return v;
};
// generated random value from given range
const randRange = (min, max) => Math.random() * max + min;
// create bubble on x and y position inside target with given hue theme
function bubble(x, y, rect, hue, target) {
// 변수 설정하기
const size = randRange(20, rect.width / 5);
const circleHue = hue + randRange(-20, 20);
const animDuration = randRange(clip(size ** 2/1000, 1), 6)
const zIndex = Math.random() < 0.1 ? 2 : -1;
// 원 만들기
const circle = document.createElement("span");
circle.className = "lit";
circle.style.left = x + "px";
circle.style.top = y + "px";
circle.style.width = size + "px";
circle.style.height = size + "px";
circle.style.background = `hsl(${circleHue}deg, 100%, 60%)`;
circle.style.zIndex = zIndex
circle.style.animationDuration = animDuration + "s";
target.appendChild(circle);
}
document.querySelectorAll("[data-lit-hue]").forEach((target) => {
const rect = target.getBoundingClientRect();
const hue = Number(target.getAttribute("data-lit-hue"));
const count = Number(target.getAttribute("data-lit-count") || 50);
for (let i = 0; i < count; i++) {
const x = randRange(0, rect.width);
const y = randRange(0, rect.height);
bubble(x, y, rect, hue, target);
}
});
}
bubbleBtn();
javascript
이 코드는 CBT(Computer-Based Testing) 시스템을 구현한 것으로 보입니다.
CBT 시스템에 대해 설명하고, 코드의 구조와 동작에 대해 간략하게 설명해 드리겠습니다.
CBT(Computer-Based Testing)란, 컴퓨터를 이용해 시험을 보는 방식을 의미합니다. CBT 시스템은 일반적인 시험보다 훨씬 더 많은 인원을 처리할 수 있으며, 자동 채점이 가능하여 채점 시간을 단축할 수 있습니다.
또한, CBT 시스템은 보안성이 뛰어나며, 시험 문제와 답안을 중앙 데이터베이스에 저장하여 관리하기 용이합니다.
이 코드에서는 fetch를 이용하여 외부 json 파일에서 문제를 가져온 후, 이를 바탕으로 시험 화면을 구성하고, 정답 확인 및 채점 기능을 제공합니다.
코드의 구조는 다음과 같습니다.
✅문제 데이터 가져오기
fetch를 이용하여 외부 json 파일에서 문제 데이터를 가져옵니다.
✅문제 생성하기
가져온 문제 데이터를 이용하여 시험 화면을 구성합니다.
✅정답 확인하기
시험 응시자가 선택한 답안을 확인하고, 채점을 수행합니다.
위와 같이 코드는 크게 3단계로 구성되어 있습니다. 각 단계의 구현 내용은 코드의 주석에 자세히 설명되어 있습니다.
코드를 간략하게 설명하자면,
querySelector 메소드를 사용해 HTML 요소를 가져와 변수에 저장합니다.
🔆fetch 함수를 사용해 JSON 형태의 데이터를 가져옵니다.
🔆가져온 데이터를 map 함수를 사용해 formattedQuestion 객체로 만들고, questionAll 배열에 저장합니다.
🔆newQuestion 함수를 호출하여 문제와 보기를 생성합니다.
🔆각각의 문제와 보기에 대한 클릭 이벤트에 대응하는 answerSelect2와 answerSelect 함수를 작성하여 정답 확인 기능을 구현합니다.
댓글