...
728x90
반응형
CBT형식의 시험지 만들기
HTML 작성하기
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>퀴즈 이펙트07</title>
<link rel="stylesheet" href="css/quiz.css">
<link rel="stylesheet" href="css/reset.css">
<link href="https://unpkg.com/pattern.css" rel="stylesheet">
<!-- 파비콘 -->
<link rel="shortcut icon" type="image/x-icon" href="img/favicon.png">
<link rel="apple-touch-icon" sizes="114x114" href="img/favicon.png">
<link rel="apple-touch-icon" href="img/favicon.png">
</head>
<body>
<header id="header">
<h1><a href="../javascript14.html">Quiz</a> <em>객관식 CBT 유형</em></h1>
<ul>
<li><a href="quizEffect01.html">1</a></li>
<li><a href="quizEffect02.html">2</a></li>
<li><a href="quizEffect03.html">3</a></li>
<li><a href="quizEffect04.html">4</a></li>
<li><a href="quizEffect05.html">5</a></li>
<li><a href="quizEffect06.html">6</a></li>
<li class="active"><a href="quizEffect07.html">7</a></li>
</ul>
</header>
<!-- //header -->
<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>
</div>
<!-- cbt__conts -->
<div class="cbt__button">
<div class="cbt__time">59분 10초</div>
<div class="cbt__submit">제출하기</div>
</div>
<div class="cbt__aside">
<div class="cbt__info">
<div class="cbt__name">
<div class="cbt__title">수험자 : <em></em></div>
<div class="cbt__score">
<span>전체 문항 : <em></em> 문항</span>
<span>남은 문항 : <em></em> 문항</span>
</div>
</div>
</div>
<!-- cbt__info -->
<div class="cbt__omr">
</div>
<!-- cbt__omr -->
</div>
<!-- cbt__aside -->
</div>
<!-- quiz__wrap__cbt -->
</main>
<!-- //main -->
</body>
</html>
HTML 정리해보기
- 기존에 사용하던 강아지(dog__wrap부분)와 다른 방식인 CBT형식으로 만들기위해 html의 구조를 다르게 변경했습니다.
- 구조는 크게 좌, 우를 나누어 좌측에는 문제를 나열하고 우측에는 omr형식의 답지를 고정하여 두는 구조입니다.
- 문제가 많아 script로 문제와 omr카드를 출력하기 때문에 문제가 들어갈 cbt__quiz부분과 omr형식의 답지가 들어갈 cbt__omr부분을 만들어 놓기만 했습니다.
CSS 작성하기
.quiz__wrap__cbt {
padding: 0 20px;
font-family: PyeongChang;
}
.cbt__header {
width: calc(100% - 300px);
background-color: #e1fae5;
border: 5px ridge #cacaca;
margin-bottom: 20px;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.cbt__header > div > div {
border: 1px solid #000;
background-color: #1c6e2a;
display: inline-block;
margin-left: 5px;
color: #fff;
padding: 10px 20px;
border-radius: 20px;
}
.cbt__conts {
width: calc(100% - 300px);
background-color: #fdfdfd;
margin-bottom: 20px;
}
.cbt__button {
width: 280px;
position: fixed;
right: 20px;
top: 90px;
padding: 10px 0;
display: flex;
justify-content: space-between;
}
.cbt__time {
position: relative;
padding: 10px 10px 10px 50px;
background-color: #52cc00;
border-radius: 30px;
font-size: 18px;
cursor: pointer;
transition: all 0.3s;
}
.cbt__time::before {
content: '';
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-alarm' width='24' height='24' viewBox='0 0 24 24' stroke-width='2' stroke='%23ffffff' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Ccircle cx='12' cy='13' r='7' /%3E%3Cpolyline points='12 10 12 13 14 13' /%3E%3Cline x1='7' y1='4' x2='4.25' y2='6' /%3E%3Cline x1='17' y1='4' x2='19.75' y2='6' /%3E%3C/svg%3E"); position: absolute;
left: 12px;
top: 9px;
width: 22px;
height: 22px;
}
.cbt__time:hover {
background-color: #0f5800;
}
.cbt__submit {
position: relative;
border-radius: 30px;
padding: 10px 10px 10px 50px;
background-color: #6aa5ff;
font-size: 18px;
cursor: pointer;
transition: all 0.3s;
}
.cbt__submit::before {
content: '';
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-edit' width='24' height='24' viewBox='0 0 24 24' stroke-width='2' stroke='%23ffffff' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M9 7h-3a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-3' /%3E%3Cpath d='M9 15h3l8.5 -8.5a1.5 1.5 0 0 0 -3 -3l-8.5 8.5v3' /%3E%3Cline x1='16' y1='5' x2='19' y2='8' /%3E%3C/svg%3E"); position: absolute;
left: 12px;
top: 9px;
width: 22px;
height: 22px;
}
.cbt__submit:hover {
background-color: #00567e;
}
.cbt__aside {
position: fixed;
right: 20px;
top: 160px;
width: 280px;
height: calc(100vh - 140px);
background-color: #fff;
border: 5px ridge #cacaca;
overflow-y: auto;
}
.cbt__quiz {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.cbt__quiz .cbt {
width: 49%;
border: 5px ridge #cacaca;
margin-bottom: 10px;
padding: 10px;
}
.cbt__info {
background-color: #e1fae5;
border-bottom: 5px ridge #cacaca;
}
.cbt__info .cbt__name {
text-align: center;
padding: 10px 0;
}
.cbt__info .cbt__title {
text-decoration: underline;
font-size: 20px;
text-underline-offset: 4px;
margin-bottom: 8px;
}
.cbt__info span {
display: inline-block;
}
.cbt__omr {
padding: 20px;
}
.cbt__omr .omr {
margin: 5px 0;
display: grid;
grid-template-columns: 50px 38px 38px 38px 38px;
grid-template-rows: 20px;
align-items: center;
}
.cbt__omr .omr input {
opacity: 0;
position: absolute;
width: 0;
height: 0;
}
.cbt__omr .omr strong {
display: inline-block;
text-align: center;
padding: 2px;
background-color: #374739;
color: #fff;
font-family: 'Helvetica Neue';
margin-right: 10px;
font-weight: bold;
}
.cbt__omr .omr label {
box-shadow: 0 0 0 1px #000;
cursor: pointer;
line-height: 0.4;
text-align: center;
width: 28px;
height: 8px;
position: relative;
font-family: 'Helvetica Neue';
position: relative;
}
.cbt__omr .omr label::after {
position: absolute;
background-color: #555;
content: '';
display: block;
top: 0;
left: 0;
width: 0;
height: 100%;
z-index: 1;
transition: width 0.1s linear;
}
.cbt__omr .omr .label-inner {
background-color: #fff;
padding: 0.25em 0.13em;
transform: translateY(-0.25em);
width: 20px;
color: #000;
}
.cbt__omr .omr input[type=radio]:checked + label::after {
width: 100%;
}
.cbt__question {
font-size: 1.4rem;
margin-bottom: 10px;
}
.cbt__question__img img {
max-width: 400px;
margin-bottom: 10px;
}
.cbt__question__desc {
border: 2px solid #cacaca;
padding: 10px;
margin-bottom: 15px;
line-height: 1.5;
}
.cbt__selects {
margin-bottom: 15px;
}
.cbt__selects label {
display: flex;
}
.cbt__selects label span {
font-size: 1rem;
line-height: 1.5;
padding: 10px 10px 10px 30px;
cursor: pointer;
color: #444;
position: relative;
}
.cbt__selects label span::before {
content: '1';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
border: 1px solid #444;
/* box-shadow: 0 0 0 1px #000; */
border-radius: 50%;
text-align: center;
font-family: 'Helvetica Neue';
font-weight: bold;
line-height: 1.1;
font-size: 0.83em;
transition: all 0.3s linear;
}
.cbt__selects label:nth-of-type(1) span::before {
content: '1';
}
.cbt__selects label:nth-of-type(2) span::before {
content: '2';
}
.cbt__selects label:nth-of-type(3) span::before {
content: '3';
}
.cbt__selects label:nth-of-type(4) span::before {
content: '4';
}
.cbt__selects input {
position: absolute;
left: -9999px;
}
.cbt__selects input:checked + label span::before {
color: #fff;
/* background-color: #000; */
box-shadow: inset 0 0 0 10px #000;
border-color: #000;
}
.cbt__selects label.correct span::before {
border-color: red;
box-shadow: inset 0 0 0 10px red;
color: #fff;
}
.cbt__desc {
background-color: #e1fae5;
padding: 10px 10px 10px 40px;
margin-bottom: 5px;
position: relative;
}
.cbt__desc.hide {
display: none;
}
.cbt__desc::before {
content: '';
position: absolute;
left: 13px;
top: 11px;
width: 20px;
height: 20px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' class='w-6 h-6'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M12 18v-5.25m0 0a6.01 6.01 0 001.5-.189m-1.5.189a6.01 6.01 0 01-1.5-.189m3.75 7.478a12.06 12.06 0 01-4.5 0m3.75 2.383a14.406 14.406 0 01-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 10-7.517 0c.85.493 1.509 1.333 1.509 2.316V18' /%3E%3C/svg%3E ");}
.cbt__keyword {
background-color: #e6ffad;
border-radius: 50px;
padding: 10px 20px 10px 40px;
position: relative;
}
.cbt__keyword::before {
content: '';
position: absolute;
left: 13px;
top: 11px;
width: 20px;
height: 20px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' class='w-6 h-6'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10' /%3E%3C/svg%3E ");
}
.cbt__quiz .cbt.good {
position: relative;
}
.cbt__quiz .cbt.good::after {
content: '';
background-image: url("../img/good.png");
background-size: contain;
background-repeat: no-repeat;
width: 200px;
height: 200px;
position: absolute;
left: -25px;
top: -30px;
}
.cbt__quiz .cbt.bad {
position: relative;
}
.cbt__quiz .cbt.bad::after {
content: '';
background-image: url("../img/bad.png");
background-size: contain;
background-repeat: no-repeat;
width: 200px;
height: 200px;
position: absolute;
left: -40px;
top: -45px;
}
@media (min-width : 1400px){
.cbt__quiz .cbt {
width: 32.3333%;
}
}
@media (max-width : 960px){
.cbt__quiz .cbt {
width: 100%;
}
}
@media (max-width : 800px){
.cbt__aside {
display: none;
}
.cbt__header {
width: 100%;
flex-direction: column;
}
.cbt__header h2 {
margin-bottom: 10px;
}
.cbt__conts {
width: 100%;
}
.cbt__button {
left: 30%;
top: 90%;
}
}
CSS 정리해보기
- 남은 시간과 남은 문항수를 알려주는 cbt__button박스와 omr카드를 적은 cbt__aside박는 position: fixed를 해주어 문제를 풀어 내려가는 동안에도 스크롤바 밖으로 나가지 않도록 했습니다.
- cbt__aside에 overflow-y: auto 의 값을 주어 넘치는 문항수에 대한 omr 카드가 cbt__aside박스 밖으로 나가지 않고 별도의 스크롤바가 생기게 만들어 주었습니다.
- 기본으로 생성되는 radio타입의 input박스 모양을 없애고 가상요소를 이용해 새로운 모양을 만들었습니다.
Javascript 작성하기
const cbtQuiz = document.querySelector(".cbt__quiz");
const cbtOmr = document.querySelector(".cbt__omr");
const cbtSubmit = document.querySelector(".cbt__submit");
const cbtName = document.querySelector(".cbt__title em");
const cbtScore = document.querySelector(".cbt__score em");
let questionAll = [];
// 문제 불러오기
const dataQuestion = () => {
fetch("json/gisa2020_01.json")
.then(res => res.json())
.then(items => {
// console.log(items)
questionAll = items.map((item, index) => {
const formattedQuestion = {
question: item.question,
number: index +1,
}
const answerChoices = [...item.incorrect_answers];
formattedQuestion.Answer = Math.floor(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.questionDesc = 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(); // 문제 만들기
})
.catch((err) => console.log(err));
}
// 문제 만들기
const exam = [];
const omr = [];
const newQuestion = () => {
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}_1" onclick="answerSelect(this)">
<label for="select${number}_1"><span>${question.choice1}</span></label>
<input type="radio" id="select${number}_2" name="select${number}" value="${number+1}_2" onclick="answerSelect(this)">
<label for="select${number}_2"><span>${question.choice2}</span></label>
<input type="radio" id="select${number}_3" name="select${number}" value="${number+1}_3" onclick="answerSelect(this)">
<label for="select${number}_3"><span>${question.choice3}</span></label>
<input type="radio" id="select${number}_4" name="select${number}" value="${number+1}_4" onclick="answerSelect(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="select${number}" id="omr${number}_1" value="${number}_0">
<label for="omr${number}_1">
<span class="label-inner">1</span>
</label>
<input type="radio" name="select${number}" id="omr${number}_2" value="${number}_0">
<label for="omr${number}_2">
<span class="label-inner">2</span>
</label>
<input type="radio" name="select${number}" id="omr${number}_3" value="${number}_0">
<label for="omr${number}_3">
<span class="label-inner">3</span>
</label>
<input type="radio" name="select${number}" id="omr${number}_4" value="${number}_0">
<label for="omr${number}_4">
<span class="label-inner">4</span>
</label>
</div>
`)
});
cbtQuiz.innerHTML = exam.join('');
cbtOmr.innerHTML = omr.join('');
// cbtName.innerHTML = prompt("수험자 성함을 적어주세요.");
};
// 정답 확인
const answerQuiz = () => {
const cbtSelects = document.querySelectorAll(".cbt__selects");
questionAll.forEach((question, number) => {
const quizSelectWrap = cbtSelects[number];
const userSelector = `input[name=select${number}]:checked`;
const userAnswer = (quizSelectWrap.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("correct");
}
const quizDesc = document.querySelectorAll(".cbt__desc");
if(quizDesc[number].innerText == "undefined"){
quizDesc[number].classList.add("hide")
} else {
quizDesc[number].classList.remove("hide")
}
});
};
const answerSelect = () => {};
cbtSubmit.addEventListener("click", answerQuiz);
dataQuestion();
javascript 정리해보기
- dataQuestion 함수
- fetch(경로/이름.json) - json파일 가져오기
- .then(res => res.json()) - 가져온 json파일을 변수 res에 json파일 형식으로 저장
- .then(items => { - 안에 있는 내용을 items라고 명명 & 화살표함수
- questionAll(빈배열)에 map()을 사용하여 items에 있는 내용을 새로운 배열로 저장합니다.
- formattedQuestion 이라는 변수에 문제의 정보를 객체 형식으로 불러옵니다.
- map()메서드의 요소자리에는 item이 인덱스 자리에는 index가 들어가있으므로 item == items 각각의 요소 입니다.
- 따라서 item.question 이란 json파일에 하나의 배열안에 저장되어있는 객체형식 요소에서 키 question에 해당하는 결과값을 의미합니다.
- answerChoices에 json파일에 있는 incorrect_answers배열을 저장합니다.
- formattedQuestion에 Answer의 값으로 Math.random을 사용해 난수에 answerChoices.length를 곱해 번호의 범위를 정하고 그 중 하나의 값을 Math.floor(버림)를 사용해 정수로 받아와 저장합니다.
- answerChoices에 splice를 통해 받아온 Answer값의 순서에 item.correct_answer(정답)을 넣어줍니다.
- answerChoices에 forEach를 사용하여 "choice"(문자열)에 번호를 추가하여 저장합니다.
- if(item.hasOwnProperty())을 사용해 각각의 내용이 있다면 그 내용을 추가해줍니다.
- 문제를 출력하는 newQuestion함수를 실행합니다.
- newQuestion 함수
- questionAll.forEach를 사용해 questionAll의 요소 개수만큼 반복하여 함수를 실행합니다.
- 미리 만들어놓은 빈 배열 exam과 omr에 push를 통해 변수를 포함한 본문 내용을 넣어줍니다.
- cbtQuiz(".cbt__quiz")에 exam을 innerHTML해줍니다. (join으로 쉼표제거)
- cbtOmr(".cbt__omr")에 omr을 innerHTML해줍니다. (join으로 쉼표제거)
- answerQuiz 함수
- cbtSelects에 각각의 cbt__selects들을 저장합니다.
- questionAll.forEach를 사용해 qustionAll의 요소 개수만큼 반복하여 함수를 실행합니다.
- quizSelectWrap = cbtSelect의 number 번째
- userSelector = 체크된 (name이 select${number}인) input박스
- userAnswer = number 번째 cbtSelects에서 체크된 (name이 select${number}인) input박스 혹은 아무 것도 없는 값의 value
- numberAnswer = userAnswer의 값이 있다면 userAnswer.slice(-1) (userAnswer의 마지막글자), 없다면 undefined
- numberAnswer와 question.Answer의 값이 같다면 cbtSelects[number]의 parentElement(부모요소)의 classList에 "good"을 추가 / 같지 않다면 "bad"를 추가하고 정답인 label의 classList에 "correct"를 추가
728x90
반응형