Код
#Руководства

Как создавать тесты и викторины на JavaScript

Разбираемся, как создавать тесты и викторины для сайта на ванильном JavaScript.

 vlada_maestro / shutterstock

Тесты и викторины хороши не только ради проверки знаний, но и как развлекательный контент, который заставляет пользователей дольше оставаться на сайте.

Чтобы их создать, можно воспользоваться сторонними сервисами, но разве это когда-нибудь останавливало хоть одного разработчика? С любовью изобретать велосипеды мы создадим собственный код для встраивания тестов на страницы.

Репозиторий проекта на GitHub

Вёрстка страницы

Тест мы поместим в файл quiz.html, чтобы его можно было вставлять с помощью iframe в другие страницы. Давайте сверстаем тест:

<div class="wrapper">
   <main class="main">
       <div class="quiz__head">
           <div class="head__content" id="head">Lorem ipsum dolor sit amet consectetur adipisicing elit. Ut ducimus odit accusamus, illum quas magni provident odio praesentium commodi sint, porro harum, minus cupiditate architecto culpa aut ex dolore officia.</div>
       </div>
       <div class="quiz__body">
           <div class="buttons">
               <div class="buttons__content" id="buttons">
                   <button class="button">Default button</button><br>
                   <button class="button button_wrong">Wrong answer</button><br>
                   <button class="button button_correct">Correct answer</button><br>
                   <button class="button button_passive">Unclicked button</button><br>
               </div>
           </div>
 
           <div class="quiz__footer">
               <div class="footer__content" id="pages">0 / 0</div>
           </div>
       </div>
      
   </main>
</div>

Теперь добавим стили:

body, html
{
   width: 100%;
   height: 100%;
   margin: 0;
   padding: 0;
   overflow: hidden;
   font-size: 16px;
   font-family: helvetica, arial;
   background: #f9f9f9;
   color: #111;
}
 
.wrapper
{
   width: 100%;
   height: 100%;
   display: table;
}
 
.main
{
   display: table-cell;
   vertical-align: middle;
   text-align: center;
}
 
.quiz-frame
{
   border: 0;
   box-shadow: 0 0 10px rgba(0,0,0,0.5);
}
 
.quiz__head
{
   font-size: 20pt;
   margin: 10px;
   margin-bottom: 50px;
}
 
.head__content
{
   padding: 5px;
}
 
.quiz__body
{
   margin: 10px;
}
 
.quiz__footer
{
   position: absolute;
   bottom: 0;
   display: block;
   width: 100%;
}
 
.footer__content
{
   padding: 5px;
}
 
.button
{
   border: 0;
   border-radius: 10px;
   background: #6477EB;
   color: #fff;
   padding: 10px 25px;
   width: 70%;
   font-size: 15pt;
   display: block;
   margin: 2px auto;
   cursor: pointer;
}
 
.button_wrong
{
   background: #EB6465;
}
 
.button_correct
{
   background: #5EB97D;
}
 
.button_passive
{
   background: #B3B3B3;
}

В файл index.html добавим iframe, чтобы подключить тест:

<iframe src="quiz.html" width="480" height="720" class="quiz-frame"></iframe>

Смотрим, что получилось:

Наверху находится сам вопрос, под ним — варианты ответов, а в самом низу — прогресс прохождения теста.

Создаём классы

Тест будет работать с помощью следующих классов:

  • Quiz — сам тест. Содержит все данные, отвечает за переход к следующему вопросу и завершение теста.
  • Question — вопрос. Содержит текст вопроса и варианты ответов.
  • Answer — ответ. Содержит текст ответа и количество очков.
  • Result — результат. Содержит финальный текст и количество очков, которое необходимо для достижения этого результата.

Вот сами классы:

//Класс, который представляет сам тест
class Quiz
{
   constructor(type, questions, results)
   {
       //Тип теста: 1 - классический тест с правильными ответами, 2 - тест без правильных ответов
       this.type = type;
 
       //Массив с вопросами
       this.questions = questions;
 
       //Массив с возможными результатами
       this.results = results;
 
       //Количество набранных очков
       this.score = 0;
 
       //Номер результата из массива
       this.result = 0;
 
       //Номер текущего вопроса
       this.current = 0;
   }
 
   Click(index)
   {
       //Добавляем очки
       let value = this.questions[this.current].Click(index);
       this.score += value;
 
       let correct = -1;
 
       //Если было добавлено хотя бы одно очко, то считаем, что ответ верный
       if(value >= 1)
       {
           correct = index;
       }
       else
       {
           //Иначе ищем, какой ответ может быть правильным
           for(let i = 0; i < this.questions[this.current].answers.length; i++)
           {
               if(this.questions[this.current].answers[i].value >= 1)
               {
                   correct = i;
                   break;
               }
           }
       }
 
       this.Next();
 
       return correct;
   }
 
   //Переход к следующему вопросу
   Next()
   {
       this.current++;
      
       if(this.current >= this.questions.length)
       {
           this.End();
       }
   }
 
   //Если вопросы кончились, этот метод проверит, какой результат получил пользователь
   End()
   {
       for(let i = 0; i < this.results.length; i++)
       {
           if(this.results[i].Check(this.score))
           {
               this.result = i;
           }
       }
   }
}
 
//Класс, представляющий вопрос
class Question
{
   constructor(text, answers)
   {
       this.text = text;
       this.answers = answers;
   }
 
   Click(index)
   {
       return this.answers[index].value;
   }
}
 
//Класс, представляющий ответ
class Answer
{
   constructor(text, value)
   {
       this.text = text;
       this.value = value;
   }
}
 
//Класс, представляющий результат
class Result
{
   constructor(text, value)
   {
       this.text = text;
       this.value = value;
   }
 
   //Этот метод проверяет, достаточно ли очков набрал пользователь
   Check(value)
   {
       if(this.value <= value)
       {
           return true;
       }
       else
       {
           return false;
       }
   }
}

Когда классы готовы, можно инстанцировать объекты (создавать экземпляры):

//Массив с результатами
const results =
[
   new Result("Вам многому нужно научиться", 0),
   new Result("Вы уже неплохо разбираетесь", 2),
   new Result("Ваш уровень выше среднего", 4),
   new Result("Вы в совершенстве знаете тему", 6)
];
 
//Массив с вопросами
const questions =
[
   new Question("2 + 2 = ",
   [
       new Answer("2", 0),
       new Answer("3", 0),
       new Answer("4", 1),
       new Answer("0", 0)
   ])
];
 
//Сам тест
const quiz = new Quiz(1, questions, results);

Здесь создан только один вопрос, чтобы не отвлекать повторяющимся кодом. Вы можете добавить их столько, сколько вам необходимо.

Остаётся только прописать логику взаимодействия с пользователем:

Update();
 
//Обновление теста
function Update()
{
   //Проверяем, есть ли ещё вопросы
   if(quiz.current < quiz.questions.length)
   {
       //Если есть, меняем вопрос в заголовке
       headElem.innerHTML = quiz.questions[quiz.current].text;
 
       //Удаляем старые варианты ответов
       buttonsElem.innerHTML = "";
 
       //Создаём кнопки для новых вариантов ответов
       for(let i = 0; i < quiz.questions[quiz.current].answers.length; i++)
       {
           let btn = document.createElement("button");
           btn.className = "button";
 
           btn.innerHTML = quiz.questions[quiz.current].answers[i].text;
 
           btn.setAttribute("index", i);
 
           buttonsElem.appendChild(btn);
       }
      
       //Выводим номер текущего вопроса
       pagesElem.innerHTML = (quiz.current + 1) + " / " + quiz.questions.length;
 
       //Вызываем функцию, которая прикрепит события к новым кнопкам
       Init();
   }
   else
   {
       //Если это конец, то выводим результат
       buttonsElem.innerHTML = "";
       headElem.innerHTML = quiz.results[quiz.result].text;
       pagesElem.innerHTML = "Очки: " + quiz.score;
   }
}
 
function Init()
{
   //Находим все кнопки
   let btns = document.getElementsByClassName("button");
 
   for(let i = 0; i < btns.length; i++)
   {
       //Прикрепляем событие для каждой отдельной кнопки
       //При нажатии на кнопку будет вызываться функция Click()
       btns[i].addEventListener("click", function (e) { Click(e.target.getAttribute("index")); });
   }
}
 
function Click(index)
{
   //Получаем номер правильного ответа
   let correct = quiz.Click(index);
 
   //Находим все кнопки
   let btns = document.getElementsByClassName("button");
 
   //Делаем кнопки серыми
   for(let i = 0; i < btns.length; i++)
   {
       btns[i].className = "button button_passive";
   }
 
   //Если это тест с правильными ответами, то мы подсвечиваем правильный ответ зелёным, а неправильный - красным
   if(quiz.type == 1)
   {
       if(correct >= 0)
       {
           btns[correct].className = "button button_correct";
       }
 
       if(index != correct)
       {
           btns[index].className = "button button_wrong";
       }
   }
   else
   {
       //Иначе просто подсвечиваем зелёным ответ пользователя
       btns[index].className = "button button_correct";
   }
 
   //Ждём секунду и обновляем тест
   setTimeout(Update, 1000);
}

Смотрим, что получилось:

Когда пользователь завершит тест, то увидит свой результат:

Особенности создания разных тестов

Как вы могли заметить, это очень простой тест. Он пригодится, чтобы пользователи могли проверить, насколько хорошо они усвоили материал. Ну или просто для веселья — вот несколько тем для развлекательных тестов:

  • Кто ты из «Чародеек» (W. I. T. C. H.).
  • За кого из «Сверхъестественного» ты выйдешь замуж.
  • Твой гороскоп на сегодня.
  • На какой факультет ты бы попал в Хогвартсе.

Всё это может быть очень забавным и вовлекающим, если учитывать особенности своей аудитории. Например, для программистов есть интересные тесты на сайте tproger.ru.

Такие вопросы развлекают и помогают узнать что-то новое

Другое дело, если у вас образовательная платформа и результаты теста влияют на итоговую оценку. В этом случае данные о правильных ответах нужно хранить на сервере. Иначе их можно подсмотреть через консоль разработчика:

То же самое касается и таймеров: если вы даёте ограниченное время на прохождение теста, то время начала отсчёта должно храниться на сервере, а не в JS-коде.

Заключение

С помощью кода из статьи можно создавать сколько угодно тестов. Разве что для каждого придётся дублировать файл app.js, чтобы указать новые вопросы.

Исправить это можно с помощью HTTP-запросов — вопросы будут храниться на сервере и отправляться пользователю в виде JSON. Это очень распространённая практика в веб-разработке, которую нужно знать каждому разработчику.

Нейросети для работы и творчества!
Хотите разобраться, как их использовать? Смотрите конференцию: четыре топ-эксперта, кейсы и практика. Онлайн, бесплатно. Кликните для подробностей.
Смотреть программу
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована