WEB/JAVASCRIPT

[javascript] 자바스크립트 객관식 문제 만들기(JSON)

aimee418 2023. 4. 3. 22:47

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

- Frederick Philips Brooks
Mythical Man-Month 저자
728x90
반응형

자바스크립트와 json을 사용한 객관식 문제 만들기

 

문제를 풀어볼까요?

 

 

 

 

 

html

<!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="asset/img/favicon.png"/>
    <link rel="apple-touch-icon" sizes="114x114" href="asset/img/favicon.png"/> 
    <link rel="apple-touch-icon" href="asset/img/favicon.png"/> 
<!-- https://github.com/webstoryboy/web2023/blob/main/javascript/quiz/quizEffect07.html 선생님꺼 -->
</head>
<body>
    <header id="header">
        <h1><a href="../javascript14.html">Quiz</a><em> 객관식 확인 OMRcard 형식</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>2020년 1회 정보처리기능사 기출문제</h2>
                <div class="cbt__tap">
                    <div class="cbt__time">59분 10초</div>
                    <div class="cbt__submit">제출하기</div>
                </div>
            </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 ="asset/img/gineungsaJC2023_01_01.jpg"></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="cbt__info">
                    <div class="title">수험자 : </div>
                    <div class="score">
                        <span>전체 문제수 : 60문항</span>
                        <span>남은 문제수 : 59문항</span>
                    </div>
                </div>
                <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_1">
                        <label for="omr0_2">
                            <span class="label-inner">2</span>
                        </label> 
                        <input type="radio" id="omr0_1">
                        <label for="omr0_3">
                            <span class="label-inner">3</span>
                        </label> 
                        <input type="radio" id="omr0_1">
                        <label for="omr0_4">
                            <span class="label-inner">4</span>
                        </label> 
                    </div> -->
                </div>
            </div>
        </div>
        </main>
        </body>
        </html>

✈.

01.  객관식 문제를 먼저 html으로 작성

02.  json파일에 문제에 대한 데이터를 담기

03. 문제에 대한 html을 자바스크립트로 json데이터를 불러오는 식으로 바꿈

 

 

 

css

.cbt__selects{
  margin-bottom: 15px;
}
.cbt__selects label{
  display: flex;
}
.cbt__selects label span{
  font-size: 1rem;
  line-height: 1.5;

  cursor: pointer;
  color:#444;
  position: relative;
  padding: 10px 10px 10px 30px;
}


.cbt__selects label span::before {
  content: '1';
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 20px;
  height: 20px;
  /* box-shadow: 0 0 0 1px #000; */
  border: 1px solid #444;
  border-radius: 50%;
  text-align: center;
  font-family: 'Arial, Helvetica, sans-serif';
  font-weight: bold;
  line-height: 1.1;
  font-size: 0.83em;
  background-color: #fff;
  transition: all 0.25s;
}
.cbt__selects label:nth-of-type(1) span::before {
  content: '1';
  padding-top: 1px;
}
.cbt__selects label:nth-of-type(2) span::before {
  content: '2';
  padding-top: 1px;
}
.cbt__selects label:nth-of-type(3) span::before {
  content: '3';
  padding-top: 1px;
}
.cbt__selects label:nth-of-type(4) span::before {
  content: '4';
  padding-top: 1px;
}
.cbt__selects input{
  position: absolute;
  left: -9999px;
}
.cbt__selects input:checked + label span::before {
  color: #fff;
  background-color: #000;
  box-shadow: inset 0 0 0 10px #000;
}
.cbt__selects{}


.cbt__desc {
  background-color: #f5f5f5;
  padding: 10px 20px 10px 40px;
  margin-bottom: 5px;
  position: relative;
}
/* header */
#header {
    background-color: #222222;
    color: #fff;
    padding: 10px;
    position: fixed;
    left: 0;
    top: 0;
    width: 100%;
    display: flex;
    justify-content: space-between;
    z-index: 999;

}
#header::before {
    content: '';
    border: 4px ridge #ccc;
    position: absolute;
    left: 5px;
    top: 5px;
    width: calc(100% - 10px);
    height: calc(100% - 10px);
}
#header h1{
    font-size: 28px;
    padding: 5px 5px 5px 10px;
    font-family: 'DungGeunMo';
    z-index: 10;
    position: relative;
}
#header h1 a {
    color: #fff;
}
#header h1 em{
    font-size: 0.5em;
}
#header ul {
  padding: 5px;
}
#header li {
  display: inline;
  z-index: 10;
  position: relative;;
}
#header li a {
    color: #fff;
    font-family: 'DungGeunMo';
    border: 1px dashed #fff;
    display: inline-block;
    padding: 5px;
}
#header li.active a,
#header li a:hover {
    background-color: #fff;
    color: #666;
} 

/* footer */
#footer {
    position: fixed;
    left: 0;
    bottom: 0;
    width: 100%;
    background-color: #d3d3d3;
    text-align: center;
}
#footer a{
    color: #fff;
    padding: 20px;
    display: block;
    font-family: 'DungGeunMo';
    z-index: 10;
    position: relative;
}
#footer ::before{
    content: '';
    border: 4px ridge #333;
    position: absolute;
    left: 5px;
    top: 5px;
    width: calc(100% - 16px);
    height: calc(100% - 12px);
    
}
#main {
    padding: 100px 0;
}
/* cbt 유형 */
.quiz__wrap__cbt {
  padding: 0 20px;
  font-family: 'NanumSquareNeo';
  
}
.cbt {
  background-color: #fff;
}
.cbt__header {
  
  width: calc(100% - 300px); 
  background-color: #fffde4;
  border: 8px ridge #ccc;
  padding: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;

}
.cbt__header h2 {
  font-weight: 700!important;
}
/* .cbt__time {
  position: relative;
  padding-left: 40px !important;
  margin-bottom: 3px 0;
  float: right;
}
.cbt__time::before {
  content:'';
  position:absolute;
  left: 10px;
  top: 3px;
  width: 22px;
  height: 22px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' class='w-6 h-6'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z' /%3E%3C/svg%3E ");
} */
.cbt__submit {
  position: relative;
  padding-left: 40px !important;
  float: right;
  margin: 3px 0;
  cursor: pointer;

}
.cbt__submit::before {
  content:'';
  position:absolute;
  left: 10px;
  top: 3px;
  width: 22px;
  height: 22px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' class='w-6 h-6'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5' /%3E%3C/svg%3E ");
}
.cbt__header > div > div {
  background-color: #006ccb;
  display: inline-block;
  margin: 5px;
  color: #fff;
  padding: 5px 15px; 
  border-radius: 20px;
}
.cbt__conts {
    width: calc(100% - 300px); 
    /* border: 8px ridge #ccc; */
    margin: 20px 0;
   
}
.cbt__tap {
  position: fixed;
  top: 100px;
  right: 30px;
}
.cbt__aside {
    position: fixed;
    right: 20px;
    top: 150px;
    width: 280px;
    height: calc(100vh - 140px);
    background-color: #fff;
    border: 8px ridge #ccc;
    overflow-y: auto;
    
}

.cbt__quiz {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}
.cbt__quiz .cbt {
  width: 49%;
  border: 8px ridge #ccc;
  margin-bottom: 10px;
  padding: 20px;
}

.cbt__quiz .cbt.good {
  position: relative;
}
.cbt__quiz .cbt.good::after {
  content: '';
  background-image: url(../asset/img/O.png);
  background-size: contain;
  background-repeat: no-repeat;
  width: 200px;
  height: 200px;
  position: absolute;
  left: 0;
  top: 0;
}
.cbt__quiz .cbt.bad {
  position: relative;
}
.cbt__quiz .cbt.bad::after {
  content: '';
  background-image: url(../asset/img/X.png);
  background-size: contain;
  background-repeat: no-repeat;
  width: 200px;
  height: 200px;
  position: absolute;
  left: 0;
  top: 0;
}

.cbt__info {
  background-color: #fffde4;
  border-bottom: 8px ridge #cacaca;
  padding: 20px;
  text-align: center;
  position: fixed;
  width: 266px;
  z-index: 999;
}
.cbt__time {
  position: relative;
  padding-left: 40px !important;
  margin-bottom: 3px 0;
  /* float: right; */
}
.cbt__time::before {
  content:'';
  position:absolute;
  left: 10px;
  top: 3px;
  width: 22px;
  height: 22px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' class='w-6 h-6'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z' /%3E%3C/svg%3E ");
}
.cbt__info .title {
  text-decoration: underline;
  text-underline-offset : 4px;
  margin-bottom: 5px;
}
.cbt__info span {
  display: inline-block;
}
.cbt__omr  {
  margin-top: 110px;
}
.cbt__omr .omr {
  margin: 5px 0;
  display: grid;
  padding: 5px 10px 3px 20px;
  grid-template-columns: 50px 30px 30px 30px 30px; 
  grid-template-rows: 20px;
  align-items: center;
}
.cbt__omr .omr input {
  opacity: 0;
  position: absolute;
  width: 0;
  height: 0;
}
.cbt__omr .omr strong {
  display: inline-block;
  text-align: center;
  padding: 2px;
  background-color: #ccc;
  color: #fff;
  font-family: 'Helvetica Neue';
  font-weight: bold;
  margin-right: 5px;
}

.cbt__omr .omr label {
  box-shadow: 0 0 0 1px #333;
  cursor: pointer;
  line-height: 0.4;
  text-align: center;
  /* width: 28px; */
  width: 25px;
  height: 8px;
  position: relative;
  font-family: 'Helvetica Neue';
}
.cbt__omr .omr label::after {
  background-color: #555;
  content: '';
  display: block;
  top: 0;
  left: 0;
  width: 0;
  height: 100%;
  position: absolute;

  z-index: 1;
}

.cbt__omr .omr input[type=radio]:checked + label::after {
  width: 100%;
}
.cbt__omr .omr .label-inner {
  background-color: #fff;
  /* width: 20px; */
  padding: 0.25em 0.13em;
  transform: translateY(-0.25em);
  color: #313e55;
}


.cbt__question {
  font-size: 1.4rem;
  margin-bottom: 10px;
}
.cbt__question__img {
  max-width: 400px;
}
.cbt__question__desc {
  border: 2px solid #eee;
  padding: 10px;
  margin-bottom: 15px;
}
.cbt__desc .hide {
  display: none;
}
.cbt__desc::before {
  content: '';
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' class='w-6 h-6'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25' /%3E%3C/svg%3E%0A");
  position: absolute;
  left: 15px;
  top: 10px;
  width: 20px;
  height: 20px;
}
.cbt__keyword {
  background-color: #fffde4;
  padding: 10px 20px 10px 40px;
  position: relative;
}
.cbt__keyword::before {
  content: '';
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' class='w-6 h-6'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z' /%3E%3C/svg%3E%0A");
  position: absolute;
  left: 15px;
  top: 10px;
  width: 20px;
  height: 20px;
}
.cbt__selects label.correct span::before {
    border-color: red;
    box-shadow: inset 0 0 0 10px red;
    color: #fff;
}

 

 

 

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 cbtScore = document.querySelector(".cbt__score");
        let quizScore = 0;

        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.ceil(Math.random() * answerChoices.length +1); //정답을 랜턴으로 불러오기
                    formattedQuestion.answer = Math.floor(Math.random() * answerChoices.length) +1; //정답을 랜턴으로 불러오기
                    answerChoices.splice(formattedQuestion.answer ,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 = () => {
            let exam = [];
            let 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="select${number+1}_0" onclick="answerSelect(this)">
                            <label for="select${number}_1"><span>${question.choice1}</span></label>
                        
                            <input type="radio" id="select${number}_2" name="select${number}" value="select${number+1}_1" onclick="answerSelect(this)">
                            <label for="select${number}_2"><span>${question.choice2}</span></label>
                    
                            <input type="radio" id="select${number}_3" name="select${number}" value="select${number+1}_2" onclick="answerSelect(this)">
                            <label for="select${number}_3"><span>${question.choice3}</span></label>
                        
                            <input type="radio" id="select${number}_4" name="select${number}" value="select${number+1}_3" 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> 
                `)
            });

            let score = `
                전체 문제 수 : ${questionAll.length}문항<br>
                남은 문제 수 : 59문항
            `;

            cbtQuiz.innerHTML = exam.join('');
            cbtOmr.innerHTML = omr.join('');
            cbtScore.innerHTML= score;
        }
        //정답확인
        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")

                }

            });
            document.querySelector(".score").innerHTML=`총 점수는 ${Math.ceil((quizScore/questionAll.length)*100)}점 입니다.`
        }

            const answerSelect = () =>{

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

✈. 

fetch('https://example.com/api/data')
  .then(response => response.json())
  .
  .

 
then(data => console.log(data))
  .
  .
catch(error => console.error(error));
fetch fetch()함수는 일반적으로 웹 개발에서 API(Application Programming Interface) 또는 서버에서 데이터를 검색하는 데 사용