Blinking Hello Kitty Angel

javascript

자바스크립트 퀴즈 사이트 만들기 7-1

xoouxa 2023. 4. 4. 01:03

“ 지연되는 프로젝트에 인력을 더 투입하면 오히려 더 늦어진다. ”

- Frederick Philips Brooks
Mythical Man-Month 저자
728x90

 

안녕하세요 ヾ(≧▽≦*)o 오늘은 자바스크립트 마우스 이펙트 일곱 번째 만드는 방법에 대해 총정리를 해보도록 하겠습니다.

이번에도 다섯번째와 같이 정보처리기능사 60문제를 주제로 퀴즈 이펙트를 만들어 보도록 하겠습니다.

아직 완벽하게 완성하진 않았지만 열심히 한 걸 토대로 블로그를 작성해 보도록 하겠습니다 ~! ( •̀ ω •́ )✧

https://xoouxa58.tistory.com/54  : 저번 유형 바로가기

 

💛이번에 만든 유형은 반응형 , CBT 유형으로 OMR카드를 함께 만들어 주었습니다.

 

💛 What i made  

 

 

How to coding ?

<!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/reset.css">
    <link rel="stylesheet" href="css/quiz.css">
<!-- 
    파비콘 -->
    <link rel="shortcut icon" type="image/x-icon" href="img/20060403.jpg"/>
    <link rel="apple-touch-icon" sizes="114x114" href="img/"/>
    <link rel="apple-touch-icon" href="assets/ico/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>2007년 1회 정보처리기능사 기출문제</h2>
                
            </div>
            <div class="cbt__conts">
                <div class="cbt__quiz">
                    <!-- <div class="cbt good">
                        <div class="cbt__question">1.객체지향 프로그램에서 데이터를 추상화하는 단위는?</div>
                        <div class="cbt__question__img"></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">2. 다음 빈칸을 채우시오.</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="ddd">
                    <div class="cbt__time"> T : 59m 10s</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>100</em>문제</span>
                                <span>남은 문제수 : <em></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 -->

HTML을 설정해 줍니다.

 

중간에 주석 처리가 돼 있는 부분은 퀴즈이펙트 5번에서 했던 것을 응용해 스크립트 부분에서 반복문을 통해 문제의 수만큼 반복시켜 줄 부분이거나 계속 바뀌는 데이터로 스크립트를 통해 작업 해 줄 예정이라 주석처리 해줍니다.


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를 만들어주고 작업해 주기 위해서 "cbt__quiz", "cbt__omr", "cbt__submit" 클래스 이름을 가진 세 개의 HTML 요소를 선택하고

빈 배열인 questionAll을 초기화 합니다.

다중이로 여러가지를 선택해야하기 때문에 All 선택을 해주어야 합니다.

그리고 fetch() 함수를 사용하여 "json/hisa2020_01.json" 파일을 가져와 res.json()함수로 json 객체로 변환합니다.

그리고 변환된 객체 각각의 항목(item)에 대해 🐣map()함수를 사용해 formattedQuestion 객체를 만듭니다.

* question: 문제 

* number: 문제 번호(index + 1)

또한, incorrect__answers 배열에서 랜덤으로 선택한 인덱스에 ccorrext_answer을 삽입하여 문제에 대한 보기를 추가합니다.

그리고 반복문이기 때문에 forEach 함수를 사용해 각 보기를 formattedQuestion 객체에 추가합니다.

 

만약, item 객체에 question_desc(문제에 대한 설명), question_img(문제에 대한 이미지), desc(해설) 속성이 있다면

formattedQuestion 객체에 추가합니다.

마지막으로 newQuestion() 함수를 호출해 HTML 문서에 퀴즈를 생성합니다. 

 

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

 

🐣여기서 잠깐 !  map() 함수를 사용하는 이유

map함수는 리스트나 이터레이터 등의 반복 가능한 객체를 받아 해당 객체의 모든 요소에 대해 지정된 함수를 적용해

그 결과를 새로운 이터레이터(iterator) 객체로 반환합니다.

map()함수를 쓰면 좋은 점

1. 코드의 간결성 - map함수는 반복문을 사용해 하나씩 요소를 처리하는 대신 한 줄의 코드로 모든 요소에 대해 함수 적용  가능

2. 함수 재사용성 - 함수를 따로 정의해 map()함수와 사용하면 해당 함수를 다른 곳에서도 사용할 수 있습니다.

3. 병렬 처리 가능 - map()함수는 병렬 처리를 적용하기 쉬워 대용량 데이터를 처리할 때 유용합니다. 🐣

 

다음은 문제를 넣어주기 위한 코드입니다.

//문제 만들기
        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"="select1"><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"="select2"><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"="select3"><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"="select4"><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(``);
        }

먼저 exam과 omr 두 개의 빈 배열을 선언합니다. 그 뒤 questionAll  배열에서 forEach 메서드를 사용해 각각의 문제에 대해

HTML 요소를 생성합니다. 생성된 HTML 요소는 exam배열과 omr 배열에 각각 push 됩니다.

exam 배열에 추가된 HTML 요소는 CBT화면에서 문제와 보기, 해설이 출력됩니다.

문제는 cbt_question 클래스를 가진  div 요소 안에 출력 되고, 보기는 cbt_selects 클래스를 가진 div 요소에 라디오 버튼과 함께 출력됩니다., 또한 보기를 선택하면 anwserSelect() 함수가 실행되도록 onclick 이벤트 핸들러가 등록 돼 있습니다.

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

 

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

각 문제는 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 ? parseInt(userAnswer.slice(-1)) : undefined;

                console.log(typeof(numberAnswer));

                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>
</body>
</html>

 

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

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

그리고 cbtSelects 클래스를 가진 부모 요소에 good 클래스 또는 bad 클래스를 추가해 사용자가 선택한 푼 문제들을 제출했을 때 그 답을

채점해 주는 기능을 넣어 동그라미 표시와 틀리면 틀렸단 표시가 화면에 출력되도록 이미지를 선택해 클래스를 넣어줍니다.

 

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

 

answerSelect 함수는 선택한 답이 없을 때 사용되어 현재는 빈 함수입니다.

 

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

 

마지막으로, 반응형으로 처리해 주기 위해서 CSS를 수정해 줍니다.

/* 미디어 쿼리 */
@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 {
        position: relative;
    }
    .ddd{
        position: absolute;
        left: 100px;
        top: 100px;
    }
    .aaa {
        display: none;
        
    }
    .cbt__header {
        width: 100%;
        flex-direction: column;
    }
    .cbt__header h2 {
        margin-bottom: 10px;
    }
    .cbt__conts {
        width: 100%;
    }
    
}

미디어쿼리를 이용해 반응형 작업을 해줍니다.


 

🥵 잘 모르는 속성

속성 설명
hasOwnProperty() 객체에 특정 속성이 존재하는지 여부를 나타내고 불리언 값을 반환합니다. hasOwnProperty 는 프로퍼티의 존재 유무를
판단하는 것이지, 프로퍼티의 값을 확인하는 것이 아니기
때문에 프로퍼티 값이 undefined나 null이어도 true를
반환합니다.
parentElement 부모 태그 찾기입니다. 형식 : 기준태그. parentElement
map() 데이터가 배열로 감싸고 안에 객체가 있어 불러올 경우
사용됩니다.
nth-of-type 동일한 유형의 형제 사이의 위치를 기준으로 요소를
일치시킵니다.
onclick HTML 요소의 클릭 이벤트를 처리하기 위한 속성입니다.
onclick 속성을 사용하면, HTML과 자바스크립트를 연결하여 동적인 웹 페이지를 만들 수 있습니다.
하지만 onclick 속성은 인라인 스크립트 방식으로
자바스크립트 코드를 작성해야 하기 때문에 코드가
길어지거나 복잡해질 경우에는 유지보수가 어려울 수
있습니다

 

🥵주의할 점🥵

이 코드는 오타가 하나라도 나면 출력이 안 되기 때문에 유의해야 합니다 !

 

 

📜 코드보기

https://github.com/leeyouna21/web2023/blob/master/javascript/quiz/quizEffect07.html

 

GitHub - leeyouna21/web2023: 수업시간에 배운 사이트입니다.

수업시간에 배운 사이트입니다. Contribute to leeyouna21/web2023 development by creating an account on GitHub.

github.com

 

🥰 오늘도 감사합니다.