728x90
반응형

 

 

 

 

 

완성 화면

 

 

 

 

 

코드 보기 / 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 문서에 표시한다.

사용자는 자신의 시험 결과를 확인할 수 있다.

 

 

 

 

 

 

 

 

+ Recent posts