JAVASCRIPT

(7) 자바스크립트 퀴즈 사이트 만들기 추가정리 !

Kim do hyun 2023. 4. 4. 20:45
728x90
반응형

퀴즈 사이트 만들기 일곱번째 추가 정리

 

JAVASCRIPT

<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 cbtStart = document.querySelector(".cbt__start")
    const cbtMin = cbtStart.querySelector(".cbt__start .minimal")

    cbtMin.addEventListener("click", ()=>{
        cbtStart.style.display = "none"
    });

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

    //데이터 가져오기
    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.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="img/${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('');
    }

    //정답 확인
    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 answerSelect2 = (elem) => {
        const answer = elem.value;
        const answerNum = answer.split("_");

        const select = document.querySelectorAll(".cbt__omr .omr");        //omr 100개
        const label = select[answerNum[0]].querySelectorAll("input");   //보기 4개
        label[answerNum[1]-1].checked = true;

        const answerInputs = document.querySelectorAll(".cbt__selects input:checked")
        cbtRest.innerHTML = questionLength - answerInputs.length;
    }

    // 보기 체크2
    const answerSelect = (elem) => {
        const answer = elem.value;
        const answerNum = answer.split("_");

        const select = document.querySelectorAll(".cbt__quiz .cbt");        //omr 100개
        const label = select[answerNum[0]].querySelectorAll("input");   //보기 4개
        label[answerNum[1]-1].checked = true;

        const answerInputs = document.querySelectorAll(".cbt__selects input:checked")
        cbtRest.innerHTML = questionLength - answerInputs.length;
    }

    cbtSubmit.addEventListener("click", answerQuiz);
    dataQuestion();

    // 버블 버튼
    const bubbleBtn = () => {
        const clip = (v, min, max = Infinity) => {
        if (v < min) return min;
        else if (v > max) return max;
        else return v;
        };

        // generated random value from given range
        const randRange = (min, max) => Math.random() * max + min;

        // create bubble on x and y position inside target with given hue theme
        function bubble(x, y, rect, hue, target) {
            // 변수 만들기
            const size = randRange(20, rect.width / 5);
            const circleHue = hue + randRange(-20, 20);
            const animDuration = randRange(clip(size ** 2/1000, 1), 6) 
            const zIndex = Math.random() < 0.1 ? 2 : -1;
            // 원 만들기
            const circle = document.createElement("span");
            circle.className = "lit";
            circle.style.left = x + "px";
            circle.style.top = y + "px";
            circle.style.width = size + "px";
            circle.style.height = size + "px";
            circle.style.background = `hsl(${circleHue}deg, 100%, 60%)`;
            circle.style.zIndex = zIndex
            circle.style.animationDuration = animDuration + "s";
            target.appendChild(circle);
        }

        document.querySelectorAll("[data-lit-hue]").forEach((target) => {
            const rect = target.getBoundingClientRect();
            const hue = Number(target.getAttribute("data-lit-hue"));
            const count = Number(target.getAttribute("data-lit-count") || 50);

            for (let i = 0; i < count; i++) {
                const x = randRange(0, rect.width);
                const y = randRange(0, rect.height);
                bubble(x, y, rect, hue, target);
            }
        });
    }

    bubbleBtn();

</script>

버블버튼

이 코드는 JavaScript에서 상수 bubbleBtn을 선언하고, 그 값으로 화살표 함수를 할당하는 것입니다.

할당된 화살표 함수는 clip이라는 내부 함수를 정의하고, 그 함수를 반환합니다.

내부 함수 clip은 3개의 매개변수 v, min, max를 가지며, v가 min과 max 사이의 값으로 제한됩니다. 만약 v가 min보다 작으면 min 값으로 대체되고, v가 max보다 크면 max 값으로 대체됩니다. v가 min과 max 사이에 있으면 그대로 반환됩니다.

예를 들어, clip(3, 5, 10)을 호출하면 3이 5와 10 사이에 없으므로 5가 반환됩니다. clip(7, 5, 10)을 호출하면 7이 5와 10 사이에 있으므로 7이 반환됩니다. clip(15, 5, 10)을 호출하면 15가 5와 10 사이에 없으므로 10이 반환됩니다.

이 코드는 주로 수학적 계산에서 사용되는 값을 제한하는 데 유용합니다.

 

보기체크

이 코드는 JavaScript에서 answerSelect2라는 화살표 함수를 선언하고, elem 매개변수를 받습니다.

elem의 value 속성을 사용하여 answer 변수에 할당합니다. 그리고 answer 문자열을 _를 기준으로 분할하여 배열로 만들어 answerNum 변수에 할당합니다.

다음으로, document.querySelectorAll 메소드를 사용하여 .cbt__omr 클래스 내부의 모든 .omr 클래스 요소를 선택합니다. 그리고 answerNum 배열의 첫 번째 요소에 해당하는 요소를 선택합니다.

선택된 요소의 .querySelectorAll 메소드를 사용하여 그 안에 있는 모든 input 요소를 선택하고, answerNum 배열의 두 번째 요소에서 1을 뺀 값에 해당하는 요소를 선택합니다. 그 요소의 checked 속성을 true로 설정하여 선택한 값을 표시합니다.

마지막으로, document.querySelectorAll 메소드를 사용하여 .cbt__selects input:checked 선택자에 해당하는 모든 요소를 선택하고, 그 요소의 개수를 questionLength에서 뺀 값을 cbtRest 요소에 출력합니다.

이 코드는 선택된 값을 표시하는 간단한 기능을 구현하기 위해 사용될 수 있습니다. 주로 시험 시스템에서 문제를 풀 때 선택한 답안을 표시하는 데 사용될 수 있습니다.

보기체크2

이 코드는 answerSelect2와 매우 유사한 기능을 수행합니다. 화살표 함수 answerSelect를 선언하고, elem 매개변수를 받습니다.

elem의 value 속성을 사용하여 answer 변수에 할당합니다. 그리고 answer 문자열을 _를 기준으로 분할하여 배열로 만들어 answerNum 변수에 할당합니다.

다음으로, document.querySelectorAll 메소드를 사용하여 .cbt__quiz 클래스 내부의 모든 .cbt 클래스 요소를 선택합니다. 그리고 answerNum 배열의 첫 번째 요소에 해당하는 요소를 선택합니다.

선택된 요소의 .querySelectorAll 메소드를 사용하여 그 안에 있는 모든 input 요소를 선택하고, answerNum 배열의 두 번째 요소에서 1을 뺀 값에 해당하는 요소를 선택합니다. 그 요소의 checked 속성을 true로 설정하여 선택한 값을 표시합니다.

마지막으로, document.querySelectorAll 메소드를 사용하여 .cbt__selects input:checked 선택자에 해당하는 모든 요소를 선택하고, 그 요소의 개수를 questionLength에서 뺀 값을 cbtRest 요소에 출력합니다.

이 코드도 선택된 값을 표시하는 간단한 기능을 구현하기 위해 사용될 수 있습니다. 하지만 answerSelect2와의 차이점은 선택한 요소를 찾는 부분에서 클래스 선택자가 다른 클래스를 사용하고 있다는 것입니다. 따라서 이 코드는 HTML 구조에 따라 선택해야 할 요소가 다를 때 사용될 수 있습니다.