본문 바로가기

『퀴즈 만들기』 페이지 기능 추가

by Recstasy 2020. 6. 23.

『자바스크립트 퀴즈 만들기』에서 작성했던 코드를 조금 수정하여 아래와 같은 퀴즈 페이지를 만들어보자. 


웹도리 Quiz

 
 


 

『자바스크립트 퀴즈 만들기』에서는 '퀴즈나라' 설계를 마치고 대략적인 퀴즈앱의 형태만 구현했다. 전체코드는 아래와 같다.  

 

[전체코드]

<div id="quiz"> </div>
     <button id="submit" style="border:1px solid tomato; margin:2px; border-radius:5px; color:tomato;"> Submit </button>
<div id="result"> </div>

<script>

//Model
const quizData = [
     {
         question : '웹개발에 주로 사용되는 프론트언어는?',
         answers : {
            a : "일본어",
            b : "다랑어",
            c : "자바스크립트"
         },
        correct : 'c'
     },
     {
         question : '웹 디자인에 사용되는 언어는?',
         answers : {
             a : '미싱',
             b : 'css',
             c : '돈까s'
         },
        correct : 'b'
     },
     {
           question : '블로그 형태로 웹사이트를 쉽게 개발할 수 있는 방식을 무엇이라고 하는가?',
           answers : {
                 a : 'CMS',
                 b : 'WAX',
                 c : 'KISWISS'
           },
        correct : 'a'
     }
]

 const quizDisplay = document.getElementById('quiz');
 const submitBtn = document.getElementById('submit');
 const resultDisplay = document.getElementById('result');
 
 function buildQuiz(){
      const output = [ ]; 
       quizData.forEach(  
           (currentQuestion, questionNum) => { 
                       const answers = [ ];     
                       for(item in currentQuestion.answers){  
                                   answers.push(`<label>
                                                <input type="radio" name="question${questionNum}" value="${item}">
                                                    ${item} : ${currentQuestion.answers[item]}
                                                </label>`);
                          }
                             output.push(`<div class="question"> ${currentQuestion.question}</div>
                                          <div class="answer">${answers.join('')}</div>`);
                    }              
       );
          quizDisplay.innerHTML = output.join('</br>');
    }
	
 function showResult(){
         const answerDisplays = quizDisplay.querySelectorAll('.answer');  
           let numCorrect = 0; 

            quizData.forEach( (currentQuestion, questionNum)=>{
                   const answerDisplay = answerDisplays[questionNum]; 
                   const selector = `input[name=question${questionNum}]:checked`;  
                   const userAnswer = (answerDisplay.querySelector(selector) || {}).value;  


                    if(userAnswer === currentQuestion.correct){    
                            numCorrect++;
                            answerDisplays[questionNum].style.color = 'lightgreen';
                    }else{
                            answerDisplays[questionNum].style.color = 'red';
                    }
            });
              resultDisplay.innerHTML = `${numCorrect} out of ${quizData.length}`; 
    }

    buildQuiz(); 
    submitBtn.addEventListener('click',showResult);   

</script>

 

 

 


1-1 퀴즈 UI변경

 

페이지 추가를 위한 업데이트에서 중요한 기능은 대략 다음과 같은 2가지이다.

 

1) UI변경(앞,뒤 퀴즈이동 버튼)

2) 퀴즈 슬라이드 기능 추가

 

사용자는 앞,뒤 버튼을 통해 퀴즈를 이동할 수 있고, '결과보기'를 통해 정답과 오답을 확인할 수 있어야한다. 이를 위해서는 슬라이드 기능과 버튼이 필요하다. 먼저 UI(버튼 포함)부터 변경해보자. 

 

<h1>웹도리 Quiz</h1>

<div class="quiz-container">
    <div id="quiz"> </div>  //퀴즈 문제가 들어갈 영역
</div>

<div class="quiz-ctrl">
   <button id="previous" class="quiz-btn"> back </button>
   <button id="next" class="quiz-btn"> next </button>
   <button id="submit" class="quiz-btn"> 결과확인 </button>
    
   <div id="result"> </div> //퀴즈 결과가 들어갈 영역
</div>

 

기존의 코드에서 'quiz-container'클래스의 <div>태그와 <button>태그 3개를 동시에 컨트롤 할 수 있는 quiz-ctrl 클래스를 추가했다. 여기서 핵심은 퀴즈 문제가 표시되는 영역을 container로 묶은 것에 있다. 앞,뒤 버튼을 클릭할 때마다 quiz-container영역의 퀴즈 문제와 선택지는 변경되며, quiz-ctrl, result영역은 유지되어야 하기 때문에 quiz-container와 quiz-ctrl을 분리해줘야 한다.

 

1-2 슬라이드 Class추가

 

서버 데이터와 연계할 수 없는 상태에서 슬라이드를 넘기는 효과를 구현하기 위해서는, CSS를 적극적으로 활용해야 한다. 해당 코드에서는 quiz-container 아래의 퀴즈 문제와 선택지를 'slide'로 묶은 뒤, 'opacity','z-index'속성을 통해 숨기기 기능을 구현할 계획이다. 이를 위해 buildQuiz()함수의 output배열에 <div class="slide">태그를 추가한다.

 

ouput.push(
`<div class="slide">
   <div class="question">${currentQuestion.question}</div>
   <div class="answer">${answers.join("&nbsp;&nbsp;")}</div>
</div>`
)

 

 


2 스타일시트 수정

 

슬라이드 기능을 css로 구현하기 위해서는 position속성에서 'relative'와 'absolute'의 관계  그리고 'opacity','z-index'속성을 이해하고 있어야 한다. relative는 상대좌표, absolute는 절대좌표인데, absolute속성은 상위 태그의 위치와 관계없이 (0,0)을 기준으로 위치한다. 반면, relative속성은 상위 태그의 위치에 종속되며, 상위 태그가 끝나는 지점에서 시작한다. 그리고 z-index와 opacity속성은 아래 사항만 기억하면 된다.

 

『z-index』 : 숫자가 클수록 상위 레이어에 해당

『opacity 』: 0:투명, 1:불투명

 

현재 퀴즈앱에서는 css의 'slide'클래스와 'active-slide'클래스 2가지를 지정한 뒤, slide클래스를 active-slide클래스보다 하위 레이어(z-index:1)에 넣고, 투명도(opacity:0)를 100%로 설정하는 방식으로 숨겨준다. 즉, active-slide클래스 부분을 slide클래스 위에 띄워서 표시하는 방식이다.

 

position:relative 설정

 

상대좌표(position:relative)를 설정할 경우, 상위 태그에 따라 퀴즈의 위치가 변하기 때문에 'slide'클래스 부분을 반드시 절대좌표로 설정해야 한다. 그리고 절대좌표의 상위 태그에서 height값이 설정되지 않을 경우 아래와 같은 상황이 발생하기 때문에 주의하자.

 

 

.quiz-container{
      position:relative;
      background-color:#f8f6f0;
      font-size:17px;
      color:#333;
      height:150px;
      margin-top:40px;
      text-align:center;
      font-weight:300;
  }
  
  .slide{
     position:absolute;
     left:0px;
     top:40px;
     width:100%;
     z-index:1;
     opacity:0;
     transition:opacity 0.5s;
  }
  
  .active-slide{
     opacity:1;
     z-index:2;
  }

 

위의 css에서 핵심속성은 position, z-index, opacity, height 4가지이다. 

 

 


3 슬라이드 기능

 

슬라이드 기능을 위해서는 앞, 뒤 버튼과 슬라이드 선택DOM을 변수를 생성한다.

 

 const submitBtn = document.getElementById('submit');
 const previousBtn = document.getElementById('previous');
 const nextBtn = document.getElementById('next');
 
 //..중략
 
 buildQuiz(); 
 
 const slides = document.querySelectorAll('.slide'); //buildQuiz()에서 생성된 slide선택
 showSlide(currentSlide); //슬라이드 호출

 

현재 next, back버튼은 동작하지 않는다. 이제 버튼을 동작하게 만들어야한다. 'next'버튼을 클릭했을 때, showSlide(페이지숫자)가 동작하면서 '페이지숫자'에 해당하는 index값의 퀴즈문제와 선택지가 화면에 나타나야 한다. 반대로 'back'버튼을 클릭한다면, 페이지숫자에서 1이 줄어들면서 index-1의 퀴즈가 생성되어야 한다. 이를 위해 showSlide(n)함수를 아래와 같이 생성해준다. 

 

function showSlide(n){

     slides[currentSlide].classList.remove('active-slide');
	 slides[n].classList.add('active-slide');
	 currentSlide = n;
     
	   if(currentSlide === 0){
	       previousBtn.style.display = 'none';
	   }else{
	       previousBtn.style.display = 'inline-block';
	   }
       
	   if(currentSlide === slides.length-1){
	       nextBtn.style.display = 'none';
		   submitBtn.style.display = 'inline-block';
	   }else{
	       nextBtn.style.display = 'inline-block';
		   submitBtn.style.display = 'none';
	   }
}

function showNextSlide(){  showSlide(currentSlide+1); }
function showPreviousSlide(){ showSlide(currentSlide-1); }

 

showSlide(n)함수가 실행되면, 현재 slides의 index(currentSlide)에 해당하는 'active-slide'클래스를 삭제한다. 그리고 인자로 입력된 'n'값의 slide index에 'active-slide'클래스명을 삽입한다.(classList.add)

 

if-else구문에서는 현재 슬라이드가 첫번째 부분인지 마지막 부분인지를 검사한 뒤 '앞', '뒤' 버튼의 삭제유무를 검토한다. (style.display = 'none') 

 

showSlide기능과 '앞', '뒤' 버튼의 이벤트까지 반영한 최종코드는 다음과 같다. 

 

[최종코드]

const quizData = [
     {
         question : '웹개발에 주로 사용되는 프론트언어는?',
         answers : {
            a : "일본어",
            b : "다랑어",
            c : "자바스크립트"
         },
        correct : 'c'
     },
     {
         question : '웹 디자인에 사용되는 언어는?',
         answers : {
             a : '미싱',
             b : 'css',
             c : '돈까s'
         },
        correct : 'b'
     },
     {
           question : '블로그 형태로 웹사이트를 쉽게 개발할 수 있는 방식을 무엇이라고 하는가?',
           answers : {
                 a : 'CMS',
                 b : 'WAX',
                 c : 'KISWISS'
           },
        correct : 'a'
     }
]
 
 const quizDisplay = document.getElementById('quiz');
 const submitBtn = document.getElementById('submit');
 const previousBtn = document.getElementById('previous');
 const nextBtn = document.getElementById('next');

 let currentSlide = 0;
 const resultDisplay = document.getElementById('result');
 
 function buildQuiz(){
      let output = [ ]; 
       quizData.forEach(  
           (currentQuestion, questionNum) => { 
                       const answers = [ ];     
                       for(item in currentQuestion.answers){  
                                   answers.push(`<label>
                                                <input type="radio" name="question${questionNum}" value="${item}">
                                                    ${item} : ${currentQuestion.answers[item]}
                                                </label>`);
                          }
                             output.push(`<div class="slide"><div class="question"> ${currentQuestion.question}</div>
                                          <div class="answer">${answers.join('&nbsp;&nbsp;&nbsp;&nbsp;')}</div></div>`);
                    }              
       );
          quizDisplay.innerHTML = output.join('</br>');
    }
	
 function showResult(){
         const answerDisplays = quizDisplay.querySelectorAll('.answer');  
           let numCorrect = 0; 

            quizData.forEach( (currentQuestion, questionNum)=>{
                   const answerDisplay = answerDisplays[questionNum]; 
                   const selector = `input[name=question${questionNum}]:checked`;  
                   const userAnswer = (answerDisplay.querySelector(selector) || {}).value;  


                    if(userAnswer === currentQuestion.correct){    
                            numCorrect++;
                            answerDisplays[questionNum].style.color = 'lightgreen';
                    }else{
                            answerDisplays[questionNum].style.color = 'red';
                    }
            });
                   
              
              submitBtn.style.display = 'none';
              resultDisplay.innerHTML = `<h3 style="color:#333;">${quizData.length}개중에서 ${numCorrect}개 맞추셨습니다.</h3>`
    }
  
	
function showSlide(n){
     slides[currentSlide].classList.remove('active-slide');
	 slides[n].classList.add('active-slide');
	 currentSlide = n;
	   if(currentSlide === 0){
	       previousBtn.style.display = 'none';
	   }else{
	       previousBtn.style.display = 'inline-block';
	   }
	   if(currentSlide === slides.length-1){
	       nextBtn.style.display = 'none';
		   submitBtn.style.display = 'inline-block';
	   }else{
	       nextBtn.style.display = 'inline-block';
		   submitBtn.style.display = 'none';
               if(document.getElementById('retry')){
                       document.getElementById('retry').style.display = 'none';
                 }
          
	   }
}

function showNextSlide(){  showSlide(currentSlide+1); }
function showPreviousSlide(){ showSlide(currentSlide-1); }

 
    buildQuiz(); 
  const slides = document.querySelectorAll('.slide');
	showSlide(currentSlide);
     previousBtn.addEventListener('click',showPreviousSlide);
     nextBtn.addEventListener('click',showNextSlide);
    submitBtn.addEventListener('click',showResult);   
    

 

위의 코드에서 모델 부분을 json형태와 결합하거나 직접 데이터를 추가하는 방식으로 수정한다면, 티스토리와 같은 수동형 블로그에서 퀴즈 앱을 쉽게 제작할 수 있다. 고객의 정보와 데이터를 결합할 필요가 없는 웹앱에서는 유용하게 사용할 수 있을 듯하다. 

 

 

댓글

최신글 전체

이미지
제목
글쓴이
등록일