코딩 공부/JAVASCRIPT

퀴즈(Quiz) 사이트를 만들어봅시다! (7)

천서리 2023. 4. 3. 23:30
QUOTE THE DAY

“ 당신이 6개월 이상 한 번도 보지 않은 코드는 다른 사람이 다시 만드는 게 훨씬 더 나을 수 있다. ”

- 이글슨 (Eagleson)
반응형

자바스크립트 퀴즈(Quiz) 사이트 만들기 

이번에 만들어 본 퀴즈 사이트는 CBT 유형입니다.


 

 


퀴즈 사이트 코드

<!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>퀴즈 이펙트06</title>


    <link rel="stylesheet" href="css/reset.css">
    <link rel="stylesheet" href="css/quiz.css">

    <link rel="shortcut icon" type="image/x-icon" href="img/ico/favicon.png"/>
    <link rel="apple-touch-icon" sizes="114x114" href="img/ico/favicon.png"/>
    <link rel="apple-touch-icon" href="img/ico/favicon.png"/>
    <style>

    </style>
</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="webd05-1.html">5-1</a></li>
            <li><a href="quizEffect06.html">6</a></li>
            <li class="active"><a href="quizEffect07.html">7</a></li>
            <li><a href="quizEffectWD.html">WD</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 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>
            <div class="cbt__aside">
                <div class="bbb">
                    <div class="cbt__time">59분 10초</div>
                    <div class="cbt__submit">제출하기</div>
                </div>
                <div class="aaa">
                    <div class="cbt__info">
                        <div>
                            <div class="cbt__title">수험자 :  <em>천설희</em></div>
                            <div class="cbt__score">
                                <span>전체 문제수 : <em>60</em>문항</span>
                                <span>남은 문제수 : <em>59</em>문항</span>
                            </div>
                        </div>
                    </div>
                    <div class="cbt__omr">
                        <!-- <div class="omr">
                            <strong>01</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>
                </div>
            </div>
        </div>
    </main>
    <!-- //main -->

 

주석 부분!

중간에 주석처리가 되어있는 부분은 전에 썼던 퀴즈 이펙트(5)에서 했던 것처럼 스크립트 부분에서 반목문을 통해 문제의 수만큼 반복시켜 줄 부분이거나 계속 바뀌는 데이터로 스크립트를 통해 처리할 부분입니다.


script 코드

 

<script>
        //선택자
        const cbtQuiz = document.querySelector(".cbt__quiz");
        const cbtOmr = document.querySelector(".cbt__omr");
        const cbtSubmit = document.querySelector(".cbt__submit");

        let questionAll = [];   //모든 퀴즈 정보
        
        //데이터 가져오기
        const dataQuestion = () => {
            fetch("json/gisa2020_01.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.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 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}_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="omr${number}" id="omr${number}_1" value="${number}_0">
                        <label for="omr${number}_1"><span class="label-inner">1</span></label>
                        <input type="radio" name="omr${number}" id="omr${number}_2" value="${number}_1">
                        <label for="omr${number}_2"><span class="label-inner">2</span></label>
                        <input type="radio" name="omr${number}" id="omr${number}_3" value="${number}_2">
                        <label for="omr${number}_3"><span class="label-inner">3</span></label>
                        <input type="radio" name="omr${number}" id="omr${number}_4" value="${number}_3">
                        <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("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();

    </script>

 

 

이번에는 JSON 파일에서 문제와 보기를 가져오고 랜덤으로 섞어서 퀴즈를 만들어 보겠습니다.

 

 

//선택자
        const cbtQuiz = document.querySelector(".cbt__quiz");
        const cbtOmr = document.querySelector(".cbt__omr");
        const cbtSubmit = document.querySelector(".cbt__submit");

        let questionAll = [];   //모든 퀴즈 정보
        
        //데이터 가져오기
        const dataQuestion = () => {
            fetch("json/gisa2020_01.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.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));
        }

 

 

먼저 "cbt__quiz", "cbt__omr", "cbt__submit" 클래스 이름을 가진 세 개의 HTML 요소를 선택하고, 빈 배열인 questionAll을 초기화합니다.

 

그리고 fetch() 함수를 사용하여 "json/gisa2020_01.json" 파일을 가져와서 res.json() 함수로 JSON 객체로 변환합니다. 그리고 변환된 객체의 각각의 항목(item)에 대해 map() 함수를 사용하여 다음과 같이 formattedQuestion 객체를 만듭니다.

  • question: 문제
  • number: 문제 번호 (index + 1)

 

또한, incorrect_answers 배열에서 랜덤으로 선택한 인덱스에 correct_answer를 삽입하여 보기를 추가합니다. 그리고 forEach() 함수를 사용하여 각 보기를 formattedQuestion 객체에 추가합니다.

 

만약, item 객체에 question_desc, question_img, desc 속성이 있다면 formattedQuestion 객체에 추가합니다. 마지막으로 newQuestion() 함수를 호출하여 HTML 문서에 퀴즈를 생성합니다.

 

만약 fetch() 함수에서 오류가 발생하면 catch() 함수에서 오류를 처리합니다.

 

 

//문제 만들기
        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}_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="omr${number}" id="omr${number}_1" value="${number}_0">
                        <label for="omr${number}_1"><span class="label-inner">1</span></label>
                        <input type="radio" name="omr${number}" id="omr${number}_2" value="${number}_1">
                        <label for="omr${number}_2"><span class="label-inner">2</span></label>
                        <input type="radio" name="omr${number}" id="omr${number}_3" value="${number}_2">
                        <label for="omr${number}_3"><span class="label-inner">3</span></label>
                        <input type="radio" name="omr${number}" id="omr${number}_4" value="${number}_3">
                        <label for="omr${number}_4"><span class="label-inner">4</span></label>
                    </div>
                `)
            });
        
            cbtQuiz.innerHTML = exam.join('');
            cbtOmr.innerHTML = omr.join('');
        }

 

 

먼저 examomr 두 개의 빈 배열을 선언합니다. 그리고 questionAll 배열에서 forEach 메서드를 사용하여 각각의 문제에 대해서 HTML 요소를 생성합니다. 생성된 HTML 요소는 exam 배열과 omr 배열에 각각 push됩니다.

 

exam 배열에 추가된 HTML 요소는 CBT(Closed Beta Test) 화면에서 문제와 보기, 해설을 출력하는 부분입니다.

각각의 문제는 cbt 클래스를 가진 div 요소 안에, 문제 번호와 문제, 보기, 해설이 출력됩니다.

문제는 cbt__question 클래스를 가진 div 요소에 출력되고, 보기는 cbt__selects 클래스를 가진 div 요소에 라디오 버튼과 함께 출력됩니다.

또한, 보기를 선택하면 answerSelect() 함수가 실행되도록 onclick 이벤트 핸들러가 등록되어 있습니다.

마지막으로 해설은 cbt__desc 클래스를 가진 div 요소에 출력됩니다.

 

omr 배열에 추가된 HTML 요소는 OMR(Optical Mark Recognition) 화면에서 각 문제의 정답을 선택하는 부분입니다.

각 문제는 omr 클래스를 가진 div 요소 안에, 문제 번호와 4개의 라디오 버튼이 출력됩니다.

라디오 버튼은 각각의 보기를 나타냅니다.

 

 

//정답 확인
        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("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();

 

answerQuiz 함수는 cbtSubmit 버튼을 클릭하면 실행됩니다.

이 함수는 모든 문제의 답을 확인하고, 사용자가 선택한 답과 정답을 비교하여 맞으면 "정답입니다."를 콘솔에 출력하고, 틀리면 "오답입니다."를 콘솔에 출력합니다.

그리고 cbtSelects 클래스를 가진 부모 요소에 good 클래스 또는 bad 클래스를 추가하여, 사용자가 선택한 답이 맞는지 틀린지에 따라 문제 옆에 색을 표시합니다.

 

또한, 사용자가 오답을 선택한 경우에는 해당 문제의 정답을 표시하기 위해 label 요소 중에서 정답에 해당하는 label 요소에 correct 클래스를 추가합니다.

 

answerSelect 함수는 선택한 답이 없을 때 사용됩니다. 현재는 빈 함수로 구현되어 있습니다.

 

마지막으로, cbtSubmit 버튼에 click 이벤트 리스너가 추가되어 있어서, 버튼을 클릭하면 answerQuiz 함수가 실행됩니다.

 

 

반응형
Adventure Time - BMO