Blinking Hello Kitty Angel

javascript

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

xoouxa 2023. 4. 5. 23:09

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

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

안녕하세요 ヾ(≧▽≦*)o 오늘은 자바스크립트 마우스 이펙트 7-2번 보충 설명을 총정리를 해보도록 하겠습니다.

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

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

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

 

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

 

저번엔 모달창 버튼 누르면 닫히게 하는 기능, 각 문제에 대한 설명과 이미지 첨부가 필요 없을 때 안 보이게 처리해 주기, 스크립트 보기와 omr을 동시에 체크해 주기, 문제 미리 보기를 방지하기 위해 블러처리해주기, 문제 시작 버튼을 꾸며주기 기능을 추가해 작업해 주었는데 오늘은 여기에 시간을 재는 기능,??분??초로 나오게 하는 기능, 이름을 집어넣어 사용자가 원하는 이름을 넣는 기능, 정답을 체크해 점수를 나타나게 하는 기능을 추가해주었습니다 😚

 

💛 What i made  

📜 How to coding ?

 

시간설정해주기

<div class="cbt__aside">
                <div class="cbt__info">
                    <div>
                        <button class="cbt__submit">제출하기</button>
                        <span class="cbt__time">59분 10초</span>
                    </div>
                    <div>
                        <div class="cbt__title">수험자 : <em class="cbt__name"></em></div>
                        <div class="cbt__score">
                            <span>전체 문제수 : <em class="cbt__length">0</em>문항</span>
                            <span>남은 문제수 : <em class="cbt__rest">0</em>문항</span>
                            <span>점수 : <em class="cbt__end__score">__</em>점</span>
                        </div>
                    </div>
                </div>

우선 저는 마지막에 최종 점수를 표시해 주기 위해서 cbt__score 안에 span으로 cbt__end__score로 변수를 하나 선언해 주었습니다.

<script>
        const cbt = document.querySelectorAll(".cbt");
        const cbtQuiz = document.querySelector(".cbt__quiz");
        const cbtOmr = document.querySelector(".cbt__omr");
        const cbtSubmit = document.querySelector(".cbt__submit");
        const cbtRest = document.querySelector(".cbt__rest");
        const cbtLength = document.querySelector(".cbt__length");
        const cbtViewSubject = document.querySelector(".cbt__view .subject");
        const cbtHeader = document.querySelector(".cbt__header h2");
        const cbtStartBtn = document.querySelector(".cbt__start__btn");
        const cbtStart = document.querySelector(".cbt__start");
        const cbtTime = document.querySelector(".cbt__time");
        const cbtEndScore = document.querySelector(".cbt__end__score");
        const youName = document.querySelector(".cbt__view .name");
        const cbtName =document.querySelector(".cbt__name");
        

        let questionAll = [];                  //모든 퀴즈 정보
        let questionLength = 0;                //전체 문제수
        let questionRest = questionLength;     //남은 문제수
        let questionTime = "";  
        let questionTimeRemain = "3600";
        let quizScore = 0;

그다음 cbtEndScore 변수를 선언해 줍니다.

또한 원래 점수를 0으로 맞춰주고 채점했을 때 그 답이 맞다면 점수가 올라갈 수 있게끔 초기값을 0으로 설정해 둡니다.

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

            cbtEndScore.innerHTML = `${Math.ceil((quizScore / (questionLength)) * 100)}`
        }

정답이 맞을 경우 그 답을 누적해 최종 결과를 보여야 함으로 quizScore++ 작업을 해줍니다.

그 뒤 cbtEndScore을 화면에 띄워주기 위해서 innerHTML 작업을 해준 뒤 퀴즈 점수와 문제 갯수를 나눈 뒤

그 나눈 값에 *100을 해주면 답을 제출했을 때 나의 점수가 화면에 표시됩니다.

 

초 단위가 한 자리가 되면 앞에 0이 오도록 시간 설정해주기

let questionAll = [];                  //모든 퀴즈 정보
        let questionLength = 0;                //전체 문제수
        let questionRest = questionLength;     //남은 문제수
        let questionTime = "";  
        let questionTimeRemain = "3600";
        let quizScore = 0;

다음은 시험을 풀 때 제한시간내에 시험을 풀 수 있게끔 하도록 시간을 설정해 줍니다.

시간은 한 시간으로 잡아 지나가게 해주었으며 00분 00초로 작업해 주고, 한 시간으로 타임을 잡기 위해서

questionTimeRemain 값을 3600으로 초기값을 설정해 줍니다.

//시간 설정
questionTime = setInterval(reduceTime, 1000);

문제의 시간은 줄어드는 시간이며 간격을 주기 위해 따로 시간 설정 작업을 해줍니다.

또한 사용자가 시작하기 버튼을 누르면 시간이 흘러가도록 설정하기 위함이기도 합니다.

// 시간 설정
        const reduceTime = () => {
            questionTimeRemain--;

            if(questionTimeRemain == 0) endQuiz();

            cbtTime.innerText = displayTime();
        }

만약 제한시간이 0이 된다면 퀴즈가 끝나도록 작업해 준 것입니다.

시간이 줄어들도록 만들기 위해선 --작업을 해줍니다. 그 뒤 innerHTML을 이용해 화면에 시험이 종료 되었다는 창을 띄워줍니다.

 

input 창에 사용자 이름 입력 값을 불러오기

<div class="cbt__view">
    	이름은 <input type="text" class="name"> 입니다.<br>
    	당신은 <span class="subject">웹디자인 기능사시험</span>을 선택했습니다.
	</div>
<button class="cbt__start__btn">시작하기</button>
</div>

사용자가 처음에 뜨는 창에 자신의 이름을 직접 입력해야 사용자의 이름이 화면에 뜨도록 하는 작업을 해줄것입니다.

우선 저는 이름이 들어가는 자리에 input으로 class name을 만들어주었습니다.

const youName = document.querySelector(".cbt__view .name");

변수를 선언해 줍니다.

//시작하기
        const startQuiz = () => {
            cbtStart.classList.add("hide"); //모달창 제거
            cbtName.innerHTML = `${youName.value}`

            //시간 설정
            questionTime = setInterval(reduceTime, 1000);
        }

이름 설정은 의외로 간단한데 시작하는 부분에 cbtName.innerHTML = `${youName.value}` 작업을 해줍니다.

화면에 띄울 수 있도록 innerHTML작업을 해주었으니 시작하는 화면에 이름을 입력할 수 있는 창이 나타납니다.

🐣🐣백틱인 거 유의하세요 ! 

문제 유형 선택창에서 JSON 파일 데이터 불러오기

//데이터 가져오기
        const dataQuestion = (value) => {
            fetch(`https://kebab000.github.io/web2023/gineungsaJSON/${value}.json`)
            // console.log(value);
            // fetch(`json/gineungsaJC2006_03.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.round(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.question_desc = item.question_desc;
                    }

                    //문제에 대한 이미지가 있으면 출력
                    if(item.hasOwnProperty("question_img")){
                        formattedQuestion.question_img = item.question_img;
                    }

                    //해설이 있으면 출력
                    if(item.hasOwnProperty("desc")){
                        formattedQuestion.desc = item.desc;
                    }

                    //console.log(formattedQuestion);
                    return formattedQuestion;
                });

                newQuestion();  //문제 만들기

                //전체 문제수
                questionLength = questionAll.length;
                cbtLength.innerHTML = questionLength;
                cbtRest.innerHTML = questionLength;

            })
            .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"><img src="https://kebab000.github.io/web2023/gineungsaJPG/${question.question_img}.jpg" alt="시험이미지"></div>
                        <div class="cbt__question__desc">${question.question_desc}</div>
                        <div class="cbt__selects">
                            <input type="radio" id="select${number}_1" name="select${number}" value="${number}_1" onclick="answerSelect2(this)">
                            <label for="select${number}_1"><span>${question.choice1}</span></label>
                            <input type="radio" id="select${number}_2" name="select${number}" value="${number}_2" onclick="answerSelect2(this)">
                            <label for="select${number}_2"><span>${question.choice2}</span></label>
                            <input type="radio" id="select${number}_3" name="select${number}" value="${number}_3" onclick="answerSelect2(this)">
                            <label for="select${number}_3"><span>${question.choice3}</span></label>
                            <input type="radio" id="select${number}_4" name="select${number}" value="${number}_4" onclick="answerSelect2(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}_1" onclick="answerSelect(this)">
                        <label for="omr${number}_1"><span class="label-inner">1</span></label>
                        <input type="radio" name="omr${number}" id="omr${number}_2" value="${number}_2" onclick="answerSelect(this)">
                        <label for="omr${number}_2"><span class="label-inner">2</span></label>
                        <input type="radio" name="omr${number}" id="omr${number}_3" value="${number}_3" onclick="answerSelect(this)">
                        <label for="omr${number}_3"><span class="label-inner">3</span></label>
                        <input type="radio" name="omr${number}" id="omr${number}_4" value="${number}_4" onclick="answerSelect(this)">
                        <label for="omr${number}_4"><span class="label-inner">4</span></label>
                    </div>
                `)
            });
        
            cbtQuiz.innerHTML = exam.join('');
            cbtOmr.innerHTML = omr.join('');

코드블럭에 onChange = changeSelect(this)를 이용해 변수 changeSelect를 만들어 줍니다.

그 부분에서 값을 가져와 변수로 만들어 줍니다.

코드블럭에 option 태그를 사용했으므로 "값option[값.SelectIndex].텍스트"를 변수로 만들어 줍니다.

데이터를 불러오는 변수에 매개변수로 부분의 값을 변수로 만든 것을 매개변수 값으로 입력해줍니다.

매개변수를 입력했으므로 데이터를 불러오는 쪽에 가져올 수 있도록 매개변수를 입력해 줍니다.

 

🥵 아직 잘 모르는 속성

속성 설명
toString() 문자열을 반환합니다.
padStart() 현재 문자열의 시작을 다른 문자열로 채워 주어진 길이를 만족하는 새로운 문자열을 반환합니다. 채워넣기는 대상 문자열의 시작인 좌측부터 적용됩니다.
setInterval 호출 사이에 고정된 시간이 지연되면 이를 반복적으로 호출을 실행합니다.
clearInterval 호출로 이전에 설정 된 시간 제한 반복 작업을 취소합니다.
Math.floor() 항상 내림하고 주어진 숫자보다 작거나 같은 가장 큰 정수를 반환합니다.

 

전체 코드

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

 

 

이번 작업은 아직 제대로 마무리 되지가 않고 코드가 복잡하고 어려워서 시간이 좀 걸리는 작업이라고 생각합니다.

하지만 그만큼 제대로 완성한다면, 정말 완성도 높고 세세한 것도 놓치지 않은 사이트가 될 거 같다고 생각합니다 !

그러니 이 글 보는 여러분 모두 다 파이팅 하셔요 ? 💪🏻

 

😍 오늘도 감사합니다.