완성 화면
코드 보기 / CSS
.quiz__choice{
padding: 20px;
border-bottom: 6px ridge #a12cd3;
font-family: 'ElandChoice';
}
.quiz__choice label {
display: flex;
}
.quiz__choice label input {
position: absolute;
clip: rect(0 0 0 0);
width: 1px;
height: 1px;
margin: -1px;
overflow: hidden;
}
.quiz__choice label span {
font-size: 20px;
line-height: 1.5;
padding: 6px;
display: flex;
cursor: pointer;
margin: 2px 0;
}
.quiz__choice label span::before {
content: '';
width: 26px;
height: 26px;
border-radius: 50%;
margin-right: 15px;
background-color: #fff;
box-shadow: inset 0 0 0 4px #63068a;
transition: all 0.2s;
flex-shrink: 0;
}
.quiz__choice label input:checked + span {
background-color: #f6ecfa;
}
.quiz__choice label input:checked + span::before {
box-shadow: inset 0 0 0 8px #63068a;
}
.quiz__check {
position: fixed;
right: 20px;
bottom: 20px;
width: 110px;
height: 110px;
line-height: 110px;
border-radius: 50%;
z-index: 1000;
background-color: #9d07f4;
text-align: center;
font-family: 'ElandChoice';
color: #fff;
cursor: pointer;
}
.quiz__info {
position: fixed;
width: 130px;
height: 50px;
border-radius: 10px;
line-height: 50px;
right: 20px;
bottom: 170px;
background-color: #9d07f4;
text-align: center;
font-family: 'ElandChoice';
color: #fff;
}
.quiz__info::after {
content: '';
position: absolute;
left: 50%;
bottom: -10px;
border-top: 10px solid #9d07f4;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
}
quiz__ choice
해당 클래스를 갖는 div 요소에 스타일을 적용한다.
이 div는 객관식 퀴즈에서 각 선택지를 감싸는 컨테이너로 사용된다.
padding: 20px
: 컨테이너의 안쪽 여백을 20px만큼 설정한다.
border-bottom: 6px ridge #a12 cd3
: 컨테이너의 아래쪽 경계선을 지정한다.
ridge는 경계선이 일반 실선이 아니라 옆에서 봤을 때 △ 모양으로 가운데가 튀어나온 스타일이다.
#a12cd3은 경계선의 색상(보라색)이다.
quiz__choice lable
:각 선택지의 라벨을 감싸는 label 요소.
display: flex
: 라벨 내부의 요소들을 수평으로 정렬한다.
quiz__choice label input
: 라벨 요소 내부에 위치한 라디오 버튼 (input 요소)에 스타일을 적용한다.
position: absolute
: 라디오 버튼을 페이지의 다른 요소와 겹치지 않도록 한다.
clip: rest(0 0 0 0)
: 라디오 버튼의 크기를 0으로 설정하여, 화면에 보이지 않게 한다.
width, height, margin, overflow
: 라디오 버튼과 라벨 텍스트를 정렬하기 위한 것이다.
quiz__choice label span
: 라디오 버튼에 대응하는 라벨 텍스트(span 요소)에 스타일을 적용한다.
font-size: 20px
:텍스트의 크기를 설정한다.
line-height: 1.5
:텍스트의 높이를 설정한다.
padding: 6px
:텍스트 내부 여백을 6px만큼 설정한다.
display: flex
:라벨 내부의 요소들을 수평으로 정렬한다.
cursor: pointer
:마우스 커서가 텍스트 위에 올라가면 포인터 모양으로 변경한다.
quiz__choice label span::before
: 선택지의 선택 항목에서 선택되지 않은 원형 요소를 생성한다.
content: ' ';
:내용을 없앤다.
width, height, border-radius
:가로 세로가 26px인 원형 모양을 만든다.
magin-right,
background-color,
box-shadow
: 내부 여백을 주고, 배경색을 설정하고 박스에 그림자 속성을 준다.
flex-shrink
:flex box 컨테이너 내에서 해당 요소가 줄어들지 않도록 한다.
quiz__choice label input:cheked + span
: 선택지의 선택 항목이 선택되었을 때의 스타일 지정한다.
input:cheked
:선택된 항목의 span 요소 스타일을 변경한다.
background-color
: 선택되었을 때의 배경색을 변경.
quiz__choice lable input:checked + span::before
: 선택된 input 요소에 인접한 span 요소의 전/ 배경 색상이 변경되도록 한다.
box-shadow: inset 0 0 0 8px #63068a;
:그림자 효과를 만든다.
inset은 그림자가 내부에 위치하도록 한다.
즉, span 요소의 내부에 테두리를 그린다.
그림자의 크기는 0 0 이며, 테두리의 두께는 8px이다.
그림자의 색상은 #63068a이다.
코드 보기 / JAVASCRIPT
//문제 정보
const quizInfo = [
{
infoType: "정보처리 기능사",
infoTime: "2010년 1회 (1월 31일)",
infoNumber : "20100101",
infoQuestion: "스택 연산에서 데이터를 삽입하거나 삭제하는 동작을 나타내는 연산으로 바르게 나타낸 것은?",
infoChoice: {
1:"ADD, SUB",
2:"LOAD, STORE",
3:"PUSH, POP",
4:"MOV, MUL"
},
infoAnswer:"3",
infoDesc: "push( ) 메서드는 배열 마지막 요소를 추가하는 스택 연산을 하고, pop( ) 메서드는 배열 마지막 요소를 삭제하는 스택 연산을 합니다."
},{
infoType: "정보처리 기능사",
infoTime: "2010년 1회 (1월 31일)",
infoNumber : "20100102",
infoQuestion: "MIMD(Multiple Instruction Multiple Data)구조를 갖는 것은?",
infoChoice: {
1: "다중 처리기",
2: "배열 처리기",
3: "벡터 처리기",
4: "파이프라인 처리기"
},
infoAnswer:"1",
infoDesc: "MIMD(Multiple Instruction Multiple Data) : <br>멀티 명령어 멀티 데이터란 뜻으로 다중 명령, 다중 데이터라고 해석할수 있습니다. 즉, 다중 처리기에 관한 내용이 됩니다."
},{
infoType: "정보처리 기능사",
infoTime: "2010년 1회 (1월 31일)",
infoNumber : "20100103",
infoQuestion: "논리적 주소에서 물리적 주소 또는 다른 논리적 주소로 번역하는 것은?",
infoChoice:{
1:"매핑",
2:"적재",
3:"재배치",
4:"주소 바인딩"
},
infoAnswer:"4",
infoDesc: "물리적 주소 또는 논리적 주소로 번역하는 것을 주소 바인딩이라고 합니다."
},{
infoType: "정보처리 기능사",
infoTime: "2010년 1회 (1월 31일)",
infoNumber : "20100104",
infoQuestion: "컴퓨터의 중앙처리장치(CPU)의 구성 부분에 해당되지 않는 것은?",
infoChoice:{
1:"주기억 장치",
2:"연산 장치",
3:"보조기억 장치",
4:"제어장치"
},
infoAnswer:"3",
infoDesc: "CPU의 주 기능은 연산과 제어입니다. 그러나 여기에 추가적으로 기억 기능이 포함될 수는 있습니다. <br> 보조기억장치의 대표적인 예로는 HDD와 SSD가 있습니다."
},{
infoType: "정보처리 기능사",
infoTime: "2010년 1회 (1월 31일)",
infoNumber : "20100105",
infoQuestion: "명령어의 주소(Address)부를 연산주소(Address)로 이용하는 주소지정방식은?",
infoChoice:{
1:"상대 Address 방식",
2:"절대 Address 방식",
3:"간접 Address 방식",
4:"직접 Address 방식"
},
infoAnswer:"4",
infoDesc: "절대주소는 실제주소(유효주소)이며 0,1,2,3... 순서로 차례대로 지정되는 주소방식입니다. <br>절대주소는 1byte 마다 연속된 16진수 번호를 부여하며 기계어의 정보가 기억되어 있습니다.<br> 상대주소는 기준주소를 기준으로 상대적으로 얼만큼 떨어져있는지 표시하는 주소입니다."
},{
infoType: "정보처리 기능사",
infoTime: "2010년 1회 (1월 31일)",
infoNumber : "20100106",
infoQuestion: "인터럽트 발생 시 인터럽트를 처리하고 원래 수행하고 있었던 프로그램으로 되돌아가는데 사용되는 레지스터는?",
infoChoice:{
1:"Stack",
2:"PC",
3:"MBR",
4:"PSW"
},
infoAnswer:"1",
infoDesc: "인터럽트 발생시 현재 수행하고 있던 프로그램 주소를 스택(Stack)에 기억시킨뒤 인터럽트 처리후 스택의 주소를 참조하여 원래 수행하던 프로그램으로 되돌아 갑니다."
}
]
//선택자
const quizWrap = document.querySelector(".quiz__wrap");
const dogWrapTrue = quizWrap.querySelectorAll(".true");
const dogWrapFalse = quizWrap.querySelectorAll(".false");
let quizScore = 0;
//출력
const updateQuiz = () => {
const exam = [];
quizInfo.forEach((question, number) => {
exam.push(`
<div class="quiz">
<div class="quiz__header">
<h2 class="quiz__title">${question.infoType} ${question.infoTime}</h2>
</div>
<div class="quiz__main">
<div class="quiz__question"><em>${number+1}</em>. ${question.infoQuestion}</div>
<div class="quiz__view">
<div class="dog__wrap">
<div class="true">정답</div>
<div class="false">오답</div>
<div class="card-container">
<div class="dog">
<div class="head">
<div class="ears"></div>
<div class="face"></div>
<div class="eyes">
<div class="teardrop"></div>
</div>
<div class="nose"></div>
<div class="mouth">
<div class="tongue"></div>
</div>
<div class="chin"></div>
</div>
<div class="body">
<div class="tail"></div>
<div class="legs"></div>
</div>
</div>
</div>
</div>
</div>
<div class="quiz__choice">
<label for="choice1${number}">
<input type="radio" id="choice1${number}" name="choice${number}" value="1">
<span>${question.infoChoice[1]}</span>
</label>
<label for="choice2${number}">
<input type="radio" id="choice2${number}" name="choice${number}" value="2">
<span>${question.infoChoice[2]}</span>
</label>
<label for="choice3${number}">
<input type="radio" id="choice3${number}" name="choice${number}" value="3">
<span>${question.infoChoice[3]}</span>
</label>
<label for="choice4${number}">
<input type="radio" id="choice4${number}" name="choice${number}" value="4">
<span>${question.infoChoice[4]}</span>
</label>
</div>
<div class="quiz__desc">정답 : <em>${question.infoAnswer}</em>번<br>${question.infoDesc}</div>
</div>
</div>
`);
});
exam.push(`
<div class="quiz__info">??점</div>
<div class="quiz__check">정답 확인하기</div>
`);
quizWrap.innerHTML = exam.join(' ');
//설명 숨기기
document.querySelectorAll(".quiz__desc").forEach(el => el.style.display = "none");
}
updateQuiz();
//정답 확인
const answerQuiz = () => {
const quizChoices = document.querySelectorAll(".quiz__choice");
//사용자가 체크한 답과 문제의 정답이 일치하는지 확인
quizInfo.forEach((question, number) => {
const userSelector = `input[name=choice${number}]:checked`;
const quizSelectorWrap = quizChoices[number];
const userAnswer = (quizSelectorWrap.querySelector(userSelector) || {}).value;
const dogWrap = quizWrap.querySelectorAll(".dog__wrap");
if(userAnswer == question.infoAnswer) {
dogWrap[number].classList.add("like");
quizScore++;
} else {
dogWrap[number].classList.add("dislike");
}
});
//해설보이기
document.querySelectorAll(".quiz__desc").forEach(el => el.style.display = "block");
//점수 보이기
document.querySelector(".quiz__info").innerHTML = Math.round((quizScore / quizInfo.length) * 100) + "점";
}
//정답 클릭
document.querySelector(".quiz__check").addEventListener("click", answerQuiz);
const exam = [ ];
: 빈 배열을 선언한다.
quizinfo.forEach((question, number)) => { .... });
: quizinfo 배열의 각 요소를 반복하여 HTML 문자열을 만든다.
forEach( )메서드를 사용하여 quizinfo 배열의 각 요소를 반복한다.
여기서 콜백 함수가 사용된다.
콜백함수의 첫 번째 매개변수인 question은 quizifo 배열의 현재 요소를 나타내며, number은 quizinfo 배열에서 현재 요소의 인덱스를 나타낸다.
exam.push( .... )
: HTML 문자열을 생성하고 exam 배열에 추가한다.
이 HTML 문자열은 quizWrap 요소에 추가된다.
` ` (백틱)을 사용하여 템플릿 리터럴을 만들고 HTML 문자열의 내용은 question 객체의 속성들을 이용하여 생성된다
예를 들어` ${question.infoType}` , ` ${question.intoTime}`은 question 객체의 infoType과infoTime 속성을 사용하여 문제의 유형과 시간 정보를 생성한다.
exam.push(`
<div class="quiz__info">??점</div>
<div class="quiz__check">정답 확인하기</div>
`);
:exam 배열에 최종 점수를 나타내는 요소와 정답 확인 버튼을 추가한다.
quiz__info와 quiz__check 클래스를 가진 요소를 추가한다.
quizWrap.innerHTML = exam.join(' ');
: exam 배열을 문자열로 변환하고, quizWrap 요소의 innerHTML에 할당하여 화면에 출력한다.
document.querySelectorAll(".quiz__desc").forEach(el => el.style.display = "none");
:quiz__desc 클래스를 가진 모든 요소에 대해 반복문을 실행하면서 style.display 속성을 none으로 주어 해당 요소를 숨긴다.
updateQuiz( );
: 함수는 quizInfo 배열을 참조하영 문제와 선택지를 생성하고, 마지막으로 정답 확인 버튼을 생성한다.
이 때, 정답 확인 버튼을 클릭하기 전까지는 정답에 대한 설명을 숨기기 위해 quiz__desc 클래스를 가진 요소를 숨기는 역할을 수행합니다.
document.querySelectorAll(".quiz__choic")
: 함수 내부의 HTML 문서에서 quiz__choice 클래스를 가진 모든 요소를 찾는다.
이는 사용자가 선택 가능한 모든 답안을 선택하는 것이다.
quizInfo.forEach((question, number) => { .... }
: quizInfo 배열을 문제에 대한 정보를 포함하고 있으며, 각 항목은 문제와 답안의 정보를 저장한다.
이 배열을 사용하여 사용자가 선택한 답과 무제의 정답을 비교하고 점수를 계산한다.
quizInfo 배열을 순회하면서 현재 문제의 정보와 문제 번호를 가져온다.
const userSelector = `input[name=choice${number}]:checked`;
: 사용자가 선택한 답안 요소의 선택된 값을 가져온다.
선택된 라디오 버튼의 값이 아닌 라디오 버튼 요소 자체의 값을 가져온다.
const quizSelectorWrap = quizChoices[number];
: quizSelectorWrap 변수는 현재 문제 번호에 해당하는 답안 요소를 가져온다. 이 변수는 quizChoic[number]
를 사용하여 quiz__choice 클래스를 가진 요소 중 numder 번째 요소를 선택한다.
const userAnswer = (quizSelectorWrap.querySelector(userSelector) || {}).value;
: userAnswer 변수를 사용하여 사용자가 선택한 답안의 값을 가져온다.
이때, quizSelectorWrap.querySelector(userSelector)를 사용하여 현재 문제 번호에 해당하는 답안 요소에서 선택된 라디오 버튼의 값을 가져온다.
만약 선택된 라디오 버튼이 없는 경우 { }를 사용하여 undefinde를 방지하고 value를 사용하여 해당 값을 가져온다.
const dogWrap = quizWrap.querySelectorAll(".dog__wrap");
: dogWrap 변수를 사용하여 quizWrap.querySelectorAll(".dog__wrap") 을 사용하여 dog__wrap 클래스를 가진 모든 요소를 가져온다. 이 배열은 문제의 각 항목에 대한 강아지 이미지 요소를 저장한다.
if(userAnswer==question.infoAnswer) {.....}
: 사용자가 선택한 답안과 문제의 정답을 비교하고, 일치하는 경우 dogWrap[number].classList.add(
"like")를 사용하여 해당 문제의 강아지 이미지 요소에 like 클래스를 추가한다.
이를 통해 사용자가 정답을 맞춘 문제에 대한 피드백(웃는 모습의 강아지)을 제공한다.
반면, 사용자가 선택한 답안이 정답과 일치하지 않는 경우
dogWrap[number].classList.add("dislike"):해당 문제의 강아지 이미지 요소에 dislike 클래스를 추가한다.
document.querySelectorAll(".quiz__desc").forEach(el => el.style.display = "block");
:HTML 문서에서 quiz__desc 클래스를 가진 모든 요소를 찾아서, el.style.display="block"을 사용하여 해당 요소를 보이게 설정한다.
이는 사용자가 문제를 푼 후에 해설을 보여주는 역할을 한다.
quiz__desc 클래스를 가진 요소는 문제와 관련된 해설을 담고 있다.이를 통해 사용자는 문제에 대한 해설을 읽을 수 있다.
document.querySelector(".quiz__info").innerHTML = Math.round((quizScore / quizInfo.length) * 100) + "점"; }
: quiz__info 클래스를 가진 요소를 찾아서 해당 요소의 내용을 계산된 점수로 변경한다.
Math.round ( ....)는 사용자의 정답 수를 전체 문제수로 나눈 비율에 100을 곱하여 백분율 점수로 변환한다.
이때, Math.round( ) 함수는 소수점 이하를 반올림한다.
document.querySelector(".quiz__check").addEventListener("click", answerQuiz);
: quiz__check 클래스를 가진 요소를 찾아서 클릭 이벤트가 발생할 때 answerQuiz 함수를 실행한다.
즉, 사용자가 정답 확인 버튼인 quiz__check를 클릭하면 answerQuiz 함수가 실행되어 사용자의 답을 채점하고, 점수를 계산한 후 결과를 HTML 문서에 표시한다.
사용자는 자신의 시험 결과를 확인할 수 있다.