Как создавать тесты и викторины на 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. Это очень распространённая практика в веб-разработке, которую нужно знать каждому разработчику.