Как создавать тесты и викторины на JavaScript
2026-02-21 12:09 Diff

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

  • 25 июн 2020
  • 0

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

 vlada_maestro / shutterstock

Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.

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

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

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

Научитесь: Профессия Фронтенд-разработчик + ИИ Узнать больше