제작

사이트제작 : CBT유형 제작 : 보충1

IC 2023. 4. 4.
CBT,OMR형식의 퀴즈 사이트를 제작해보았습니다.

<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>
    <!-- cbt__conts -->

    <div class="cbt__aside">
        <div>
        <div class="cbt__info">
            <div>
                <button class="cbt__time">59분 10초</button>
                <span class="cbt__submit">제출하기</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>
                </div>
            </div>
        </div>
        <!-- cbt__info -->
        <div class="cbt__omr">
            <!-- <div class="omr">
                <strong>1</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>
        <!-- cbt__omr -->
    </div>
    <!-- cbt__aside -->
    <div>
    </div>
</div>
    <div class="cbt__start">
        <div class="cbt_modal1">
            <h2>기능사 시험 도전하기</h2>
            <div class="cbt__choice">
                <select class="cbt_h1" name="cbtTime" id="cbtTime">
                    <option class="cbt_h2" value="gineungsaJC2005_02">정보처리기능사 2005년 2회</option>
                    <option class="cbt_h2" value="gineungsaJC2005_04">정보처리기능사 2005년 4회</option>
                    <option class="cbt_h2" value="gineungsaJC2005_05">정보처리기능사 2005년 5회</option>
                    <option class="cbt_h2" value="gineungsaJC2006_01">정보처리기능사 2006년 1회</option>
                    <option class="cbt_h2" value="gineungsaJC2006_02">정보처리기능사 2006년 2회</option>
                    <option class="cbt_h2" value="gineungsaJC2006_03">정보처리기능사 2006년 3회</option>
                    <option class="cbt_h2" value="gineungsaJC2006_05">정보처리기능사 2006년 5회</option>
                    <option class="cbt_h2" value="gineungsaJC2007_01">정보처리기능사 2007년 1회</option>
                    <option class="cbt_h2" value="gineungsaJC2007_02">정보처리기능사 2007년 2회</option>
                    <option class="cbt_h2" value="gineungsaJC2007_05">정보처리기능사 2007년 5회</option>
                    <option class="cbt_h2" value="gineungsaJC2008_01">정보처리기능사 2008년 1회</option>
                    <option class="cbt_h2" value="gineungsaJC2008_02">정보처리기능사 2008년 2회</option>
                    <option class="cbt_h2" value="gineungsaJC2008_04">정보처리기능사 2008년 4회</option>
                    <option class="cbt_h2" value="gineungsaJC2008_05">정보처리기능사 2008년 5회</option>
                    <option class="cbt_h2" value="gineungsaJC2009_01">정보처리기능사 2009년 1회</option>
                    <option class="cbt_h2" value="gineungsaJC2009_05">정보처리기능사 2009년 5회</option>
                    <option class="cbt_h2" value="gineungsaJC2010_02">정보처리기능사 2010년 2회</option>
                    <option class="cbt_h2" value="gineungsaJC2010_05">정보처리기능사 2010년 5회</option>
                    <option class="cbt_h2" value="gineungsaJC2011_01">정보처리기능사 2011년 1회</option>
                    <option class="cbt_h2" value="gineungsaJC2011_02">정보처리기능사 2011년 2회</option>
                    <option class="cbt_h2" value="gineungsaJC2011_04">정보처리기능사 2011년 4회</option>
                    <option class="cbt_h2" value="gineungsaJC2011_05">정보처리기능사 2011년 5회</option>
                </select>
                <select class="cbt_h1" name="cbtTime" id="cbtTime">
                    <option class="cbt_h2" value="gineungsaWD2009_05">웹디자인기능사 2009년 5회</option>
                    <option class="cbt_h2" value="gineungsaWD2010_01">웹디자인기능사 2010년 1회</option>
                    <option class="cbt_h2" value="gineungsaWD2010_02">웹디자인기능사 2010년 2회</option>
                    <option class="cbt_h2" value="gineungsaWD2010_04">웹디자인기능사 2010년 4회</option>
                    <option class="cbt_h2" value="gineungsaWD2010_05">웹디자인기능사 2010년 5회</option>
                    <option class="cbt_h2" value="gineungsaWD2011_01">웹디자인기능사 2011년 1회</option>
                    <option class="cbt_h2" value="gineungsaWD2011_02">웹디자인기능사 2011년 2회</option>
                    <option class="cbt_h2" value="gineungsaWD2011_04">웹디자인기능사 2011년 4회</option>
                    <option class="cbt_h2" value="gineungsaWD2011_05">웹디자인기능사 2011년 5회</option>
                    <option class="cbt_h2" value="gineungsaWD2012_02">웹디자인기능사 2012년 2회</option>
                    <option class="cbt_h2" value="gineungsaWD2012_04">웹디자인기능사 2012년 4회</option>
                    <option class="cbt_h2" value="gineungsaWD2012_05">웹디자인기능사 2012년 5회</option>
                    <option class="cbt_h2" value="gineungsaWD2013_02">웹디자인기능사 2013년 2회</option>
                    <option class="cbt_h2" value="gineungsaWD2013_04">웹디자인기능사 2013년 4회</option>
                    <option class="cbt_h2" value="gineungsaWD2013_05">웹디자인기능사 2013년 5회</option>
                    <option class="cbt_h2" value="gineungsaWD2014_01">웹디자인기능사 2014년 1회</option>
                    <option class="cbt_h2" value="gineungsaWD2014_04">웹디자인기능사 2014년 4회</option>
                    <option class="cbt_h2" value="gineungsaWD2014_05">웹디자인기능사 2014년 5회</option>
                    <option class="cbt_h2" value="gineungsaWD2015_01">웹디자인기능사 2015년 1회</option>
                    <option class="cbt_h2" value="gineungsaWD2015_03">웹디자인기능사 2015년 3회</option>
                    <option class="cbt_h2" value="gineungsaWD2015_04">웹디자인기능사 2015년 4회</option>
                    <option class="cbt_h2" value="gineungsaWD2015_05">웹디자인기능사 2015년 5회</option>
                    <option class="cbt_h2" value="gineungsaWD2016_01">웹디자인기능사 2016년 1회</option>
                    <option class="cbt_h2" value="gineungsaWD2016_04">웹디자인기능사 2016년 4회</option>
                </select>
            </div>
                <div class="button-container">
                    <button class="start-button">시작하기
                        <span class="start-button__border"></span>
                        <span class="start-button__border"></span>
                        <span class="start-button__border"></span>
                        <span class="start-button__border"></span>
                    </button>
                </div>
            </div>
        </div>
    </div>
<!-- quiz__wrap__cbt -->

</main>
<!-- //main -->

HTML

이것은 HTML 코드로 이루어진 모의고사 시험 화면입니다. 이 시험은 2020년 1회 정보처리기능사 기출문제입니다. 시험 화면은 여러 구성 요소로 이루어져 있으며, 문제와 선택지, 선택지의 설명 등을 포함합니다. 시험 화면의 왼쪽에는 수험자 정보와 시험 시간, 문제 수 등의 정보가 포함된 창이 있습니다.

 

const cbt = document.querySelector(".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");

    let questionAll = [];   //questionAll이라는 배열을 생성합니다.
    let questionLength = 0; //전체 문제수
    let questionRest = 100; //나머지
    //데이터 가져오기
    const dataQuestion = () => {
        fetch("https://dkseho9121.github.io/web2023/javascript/quiz/json/gineungsaWD2005_02.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.questionImg = 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 updateQuiz = (index) => {
        let questionnumTag = `
            ${cbtQuiz.length}문제중에 ${cbtQuiz.length - index}문제 남았습니다 
        `;
    }
    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" 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("corrent");
            }

            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");   //전체 문항 수 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");   //전체 문항 수 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();

javascript

이 코드는 CBT(Computer-Based Testing) 시스템을 구현한 것으로 보입니다.

CBT 시스템에 대해 설명하고, 코드의 구조와 동작에 대해 간략하게 설명해 드리겠습니다.

CBT(Computer-Based Testing)란, 컴퓨터를 이용해 시험을 보는 방식을 의미합니다. CBT 시스템은 일반적인 시험보다 훨씬 더 많은 인원을 처리할 수 있으며, 자동 채점이 가능하여 채점 시간을 단축할 수 있습니다.

또한, CBT 시스템은 보안성이 뛰어나며, 시험 문제와 답안을 중앙 데이터베이스에 저장하여 관리하기 용이합니다.

이 코드에서는 fetch를 이용하여 외부 json 파일에서 문제를 가져온 후, 이를 바탕으로 시험 화면을 구성하고, 정답 확인 및 채점 기능을 제공합니다.

코드의 구조는 다음과 같습니다.

문제 데이터 가져오기
fetch를 이용하여 외부 json 파일에서 문제 데이터를 가져옵니다.

 

문제 생성하기
가져온 문제 데이터를 이용하여 시험 화면을 구성합니다.

 

정답 확인하기
시험 응시자가 선택한 답안을 확인하고, 채점을 수행합니다.


위와 같이 코드는 크게 3단계로 구성되어 있습니다. 각 단계의 구현 내용은 코드의 주석에 자세히 설명되어 있습니다.

 

코드를 간략하게 설명하자면,
querySelector 메소드를 사용해 HTML 요소를 가져와 변수에 저장합니다.


🔆fetch 함수를 사용해 JSON 형태의 데이터를 가져옵니다.


🔆가져온 데이터를 map 함수를 사용해 formattedQuestion 객체로 만들고, questionAll 배열에 저장합니다.


🔆newQuestion 함수를 호출하여 문제와 보기를 생성합니다.


🔆각각의 문제와 보기에 대한 클릭 이벤트에 대응하는 answerSelect2와 answerSelect 함수를 작성하여 정답 확인 기능을 구현합니다.

댓글