Заготовка под Quiz для сайта. Форма-вопросник
Данный код представляет собой готовую заготовку для создания интерактивного квиза на вашем сайте. Квиз позволяет пользователям последовательно проходить вопросы с вариантами ответов, отслеживать прогресс и завершить форму с контактными данными.
Основные возможности
- Многошаговая навигация: пользователь переходит между вопросами с помощью кнопок «Prev» и «Next»
- Визуальное отображение прогресса: прогресс-бар и счётчик текущего шага
- Плавная анимация: переходы между вопросами происходят с CSS-анимацией
- Финальная форма: после прохождения всех вопросов пользователь заполняет номер телефона
- Валидация данных: проверка на заполненность всех полей перед отправкой
Структура файлов
Вам понадобятся три файла:
- HTML — структура квиза
- CSS — оформление и анимация
- JavaScript — логика работы
Как использовать
Шаг 1: Скопируйте HTML
Вставьте HTML-код в нужное место на вашей странице. Основной контейнер имеет класс js-quiz — это точка входа для JavaScript.
Шаг 2: Скопируйте CSS
Добавьте CSS-стили в ваш файл стилей или в тег <style> в <head>. Стили используют CSS-переменные для управления анимацией слайдера.
Шаг 3: Скопируйте JavaScript
Добавьте JavaScript-код в ваш файл скриптов или в тег <script> перед закрывающим </body>. Убедитесь, что JavaScript загружается после DOM-элементов.
Шаг 4: Кастомизация
Измените следующие элементы под ваши нужды:
- Название квиза: отредактируйте текст в
<h2>внутриquiz__steps-title - Вопросы: замените текст в тегах
<h3>каждогоquiz__step - Варианты ответов: измените текст в
<span>внутри label - Количество шагов: добавьте или удалите блоки
<div class="quiz__step"> - Не забудьте обновить
maxатрибут в<progress>(он должен быть на 1 больше, чем количество вопросов) - Финальный текст: измените содержимое
quiz__final - Плейсхолдер телефона: отредактируйте атрибут
placeholderдля поля телефона
Как переработать код под себя
Добавление новых вопросов
Скопируйте блок одного вопроса и вставьте в .js-quiz-slider:
<div class="quiz__step">
<h3>Ваш новый вопрос</h3>
<label>
<input type="radio" name="stepN" value="1" checked>
<span>ответ 1</span>
</label>
<!-- остальные варианты -->
</div>
Не забудьте:
- Изменить
nameна уникальное значение (например,step5для пятого вопроса) - Обновить
maxв<progress>(max = количество вопросов + 1)
Изменение оформления
Все CSS-классы начинаются с .quiz__ — это позволяет безопасно стилизовать элементы без конфликтов с остальным сайтом.
Обработка результатов
В методе submitForm() вы можете отправить данные на сервер:
// Вместо console.log отправьте на сервер
fetch('/api/quiz-submit', {
method: 'POST',
body: data
});
Технические детали
JavaScript класс Quiz
Класс Quiz управляет всей логикой:
- constructor() — инициализация элементов DOM и начальные настройки
- nextStep() — переход к следующему вопросу
- prevStep() — переход к предыдущему вопросу
- updateStatus() — обновление визуального отображения прогресса
- init() — подготовка квиза к работе
- submitForm() — обработка отправки формы
Селекторы, используемые в коде
| Селектор | Назначение |
|---|---|
.js-quiz | Основной контейнер квиза |
.js-quiz-steps | Контейнер с вопросами |
.js-quiz-slider | Слайдер, содержащий все вопросы |
.js-progress-step | Текущий номер шага |
.js-progress-allsteps | Всего шагов |
.js-progressBar | Элемент прогресс-бара |
.js-prev | Кнопка «Назад» |
.js-next | Кнопка «Вперёд» |
.js-quiz-final | Контейнер финальной формы |
.js-quiz-submit | Кнопка отправки |
Советы по использованию
- Тестирование: проверьте работу на мобильных устройствах — используйте DevTools
- Доступность: убедитесь, что можно навигировать только с клавиатуры
- Валидация: добавьте проверку формата телефона перед отправкой
- Аналитика: добавьте отслеживание событий (какие ответы выбирают пользователи)
- Благодарность: покажите сообщение об успешной отправке
Возможные улучшения
- Добавить обратный отсчёт времени
- Сохранять прогресс в localStorage (чтобы пользователь мог вернуться позже)
- Показать результаты квиза на финальном экране
- Интеграция с CRM-системой для автоматической обработки контактов
- Добавить конфетти или другие визуальные эффекты при завершении
Версия: 1.0
Браузеры: работает во всех современных браузерах (IE не поддерживается)
Подробный код для размещения в проекте
HTML разметка самого Квиза
<!-- КВИЗ: Основной контейнер со служебным классом js-quiz для JavaScript -->
<div class="quiz js-quiz">
<form>
<!-- БЛОК ВОПРОСОВ: Содержит заголовок, прогресс, слайдер с вопросами и кнопки навигации -->
<div class="quiz__steps js-quiz-steps">
<!-- ЗАГОЛОВОК: Содержит название квиза и информацию о прогрессе -->
<div class="quiz__steps-header">
<!-- Название квиза (отредактируйте текст по необходимости) -->
<div class="quiz__steps-title">
<h2>Название Квиза</h2>
</div>
<!-- ПРОГРЕСС: Показывает текущий шаг, общее количество шагов и визуальный прогресс-бар -->
<div class="quiz__steps-progress">
<!-- Текстовое отображение прогресса: "Шаг X из Y" -->
<!-- js-progress-step будет обновляться JavaScript при переходе -->
<!-- js-progress-allsteps устанавливается один раз при инициализации -->
<p>Шаг <span class="js-progress-step">1</span> из <span class="js-progress-allsteps">3</span></p>
<!-- HTML5 элемент progress bar для визуализации прогресса -->
<!-- min/max определяют диапазон значений, value - текущее значение -->
<!-- js-progressBar используется для обновления значения через JavaScript -->
<progress class="progressbar js-progressBar" min="1" max="4" value="1"></progress>
</div>
</div>
<!-- СЛАЙДЕР ВОПРОСОВ: Контейнер, в котором находятся все вопросы -->
<!-- Вопросы сдвигаются влево/вправо с помощью CSS transform и CSS-переменных -->
<!-- js-quiz-slider используется для управления позицией слайдера -->
<div class="quiz__steps-slider js-quiz-slider">
<!-- ШАГИ (ВОПРОСЫ): Каждый блок — это один вопрос с вариантами ответов -->
<!-- При загрузке первому шагу добавляется класс "active" -->
<div class="quiz__step">
<h3>Вопрос Шага №1</h3>
<!-- Каждый label оборачивает radio-кнопку и текст ответа -->
<!-- name="step1" связывает все radio внутри одного вопроса в группу (может быть только один выбран) -->
<!-- value используется для отправки на сервер (какой ответ выбран) -->
<!-- checked указывает на выбранный по умолчанию вариант -->
<label>
<input type="radio" name="step1" value="1" checked>
<span>ответ 1</span>
</label>
<label>
<input type="radio" name="step1" value="2">
<span>ответ 2</span>
</label>
<label>
<input type="radio" name="step1" value="3">
<span>ответ 3</span>
</label>
<label>
<input type="radio" name="step1" value="4">
<span>ответ 4</span>
</label>
</div>
<div class="quiz__step">
<h3>Вопрос Шага №2</h3>
<label>
<input type="radio" name="step2" value="1" checked>
<span>ответ 1</span>
</label>
<label>
<input type="radio" name="step2" value="2">
<span>ответ 2</span>
</label>
<label>
<input type="radio" name="step2" value="3">
<span>ответ 3</span>
</label>
<label>
<input type="radio" name="step2" value="4">
<span>ответ 4</span>
</label>
</div>
<div class="quiz__step">
<h3>Вопрос Шага №3</h3>
<label>
<input type="radio" name="step3" value="1" checked>
<span>ответ 1</span>
</label>
<label>
<input type="radio" name="step3" value="2">
<span>ответ 2</span>
</label>
<label>
<input type="radio" name="step3" value="3">
<span>ответ 3</span>
</label>
<label>
<input type="radio" name="step3" value="4">
<span>ответ 4</span>
</label>
</div>
<div class="quiz__step">
<h3>Вопрос Шага №4</h3>
<label>
<input type="radio" name="step4" value="1" checked>
<span>ответ 1</span>
</label>
<label>
<input type="radio" name="step4" value="2">
<span>ответ 2</span>
</label>
<label>
<input type="radio" name="step4" value="3">
<span>ответ 3</span>
</label>
<label>
<input type="radio" name="step4" value="4">
<span>ответ 4</span>
</label>
</div>
</div>
<!-- КНОПКИ НАВИГАЦИИ: Prev (назад) и Next (вперёд) -->
<!-- js-prev и js-next используются для управления переходами между вопросами -->
<!-- type="button" указывает, что это не submit кнопка (не отправляет форму) -->
<div class="quiz__steps-control">
<button type="button" class="js-prev">Prev</button>
<button type="button" class="js-next">Next</button>
</div>
</div>
<!-- ФИНАЛЬНЫЙ БЛОК: Появляется после прохождения всех вопросов -->
<!-- js-quiz-final имеет класс hidden при загрузке страницы и показывается JavaScript -->
<div class="quiz__final js-quiz-final hidden">
<h2>Это Финал</h2>
<p>Теперь заполни номер телефона, и мы позвоним тебе рассказать, как получить выгоду</p>
<!-- Поле телефона для сбора контактных данных пользователя -->
<!-- type="tel" указывает браузеру, что это телефонное поле (показывает числовую клавиатуру на мобильных) -->
<!-- name="phone" используется при отправке формы для идентификации поля -->
<!-- placeholder показывает подсказку формата ввода -->
<!-- required проверяет на серверной стороне, что поле не пусто -->
<input type="tel" name="phone" placeholder="+7 (___) ___-__-__" required>
<!-- Кнопка отправки формы -->
<!-- Правильный тег: <button type="submit">Отправить</button> вместо <submit> -->
<!-- js-quiz-submit используется JavaScript для обработки клика и валидации -->
<button type="submit" class="button quiz__submit js-quiz-submit">Отправить</button>
</div>
</form>
</div>CSS стилизация (заготовка) всего блока Квиза
Эта стилизация является базовой. Далее, после внедрения на сайт, нужно стилизацию дорабатывать под общую стилизацию сайта.
/* ОТЛАДКА: Визуализация границ всех элементов (кроме page и container) для понимания структуры */
/* Удалите эту строку перед развёртыванием на продакшн */
div:not(.page, .container) {
border: 1px dashed #00000033;
}
/* ОСНОВНОЙ КОНТЕЙНЕР КВИЗА */
.quiz {
display: grid;
min-height: 400px;
padding: 20px;
}
/* БЛОК ВОПРОСОВ */
.quiz__steps {
height: 100%;
display: grid;
grid-template-rows: auto 1fr;
overflow: clip;
}
/* СКРЫТИЕ БЛОКА ВОПРОСОВ */
.quiz__steps.hidden {
display: none;
}
/* ФИНАЛЬНЫЙ БЛОК */
.quiz__final {
height: 100%;
overflow: clip;
}
/* СКРЫТИЕ ФИНАЛЬНОГО БЛОКА */
.quiz__final.hidden {
display: none;
}
/* СЛАЙДЕР ВОПРОСОВ */
.quiz__steps-slider {
--active-step: 1;
transition: 300ms;
display: flex;
align-self: stretch;
height: 100%;
width: calc(100% * var(--all-steps, 3));
translate: calc(-1 * (100% / var(--all-steps, 3) * (var(--active-step) - 1)));
}
/* КАЖДЫЙ ВОПРОС (ШАГ) ВНУТРИ СЛАЙДЕРА */
.quiz__steps-slider > * {
width: 100%;
opacity: 0;
transition: 300ms;
}
/* АКТИВНЫЙ ВОПРОС */
.quiz__steps-slider > *.active {
opacity: 1;
}
/* КНОПКИ НАВИГАЦИИ (Prev / Next) */
.quiz__steps-control {
display: flex;
justify-content: space-between;
}
/* СТИЛЬ ДЛЯ LABEL С RADIO-КНОПКОЙ */
.quiz__step label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
/* РАССТОЯНИЕ МЕЖДУ ВАРИАНТАМИ ОТВЕТОВ */
.quiz__step label + label {
margin-top: 8px;
}
/* СТИЛЬ ПРОГРЕСС-БАРА */
.quiz .progressbar {
width: 100%;
}
/* КНОПКА ОТПРАВКИ ФОРМЫ */
.quiz__submit {
width: fit-content;
margin-block: 16px;
}JavaScript код с классом Quiz, включающим функционал работы квиза
/* КЛАСС QUIZ: Управляет всей логикой интерактивного квиза */
class Quiz {
/* КОНСТРУКТОР: Вызывается при создании нового объекта Quiz */
constructor(selector) {
/*
ИНИЦИАЛИЗАЦИЯ ЭЛЕМЕНТОВ DOM
Сохраняем ссылки на DOM-элементы в свойства класса
Это позволяет быстро обращаться к ним без повторного поиска
*/
/* this.quiz — основной контейнер квиза */
this.quiz = document.querySelector(selector);
/* Кнопка "Назад" */
this.prev = this.quiz.querySelector(".js-prev");
/* Кнопка "Вперёд" */
this.next = this.quiz.querySelector(".js-next");
/* Кнопка отправки формы */
this.submit = this.quiz.querySelector(".js-quiz-submit");
/* Слайдер с вопросами (управляет позицией и видимостью вопросов) */
this.stepsSlider = this.quiz.querySelector(".js-quiz-slider");
/* Элемент, который показывает общее количество вопросов */
this.progressAllSteps = this.quiz.querySelector(".js-progress-allsteps");
/* Элемент, который показывает текущий номер вопроса (обновляется при каждом переходе) */
this.progressStep = this.quiz.querySelector(".js-progress-step");
/* HTML5 элемент progress bar для визуальной индикации прогресса */
this.progressBar = this.quiz.querySelector(".js-progressBar");
/*
this.steps — коллекция всех вопросов (дочерних элементов слайдера)
HTMLCollection, который обновляется автоматически, если изменится DOM
*/
this.steps = this.stepsSlider.children;
/* Переменная для отслеживания текущего активного вопроса */
this.activeStep = 1;
/*
Общее количество вопросов (шагов)
Определяется при инициализации по количеству дочерних элементов слайдера
*/
this.countAllSteps = this.steps.length;
/* Вызываем метод инициализации */
this.init();
}
/* МЕТОД: nextStep — переход к следующему вопросу */
nextStep(e) {
/* e.preventDefault() отменяет стандартное поведение кнопки (отправка формы) */
e.preventDefault();
/*
Получаем текущий номер шага из атрибута data-step слайдера
+... преобразует строку в число (например, "2" становится 2)
*/
this.activeStep = +this.stepsSlider.dataset.step;
/* Проверяем, не находимся ли мы на последнем вопросе */
if (this.activeStep < this.countAllSteps) {
/* Если не на последнем — переходим к следующему вопросу */
this.activeStep += 1;
/* updateStatus() обновляет визуальное отображение (прогресс, позицию слайдера и т.д.) */
this.updateStatus();
} else {
/*
Если на последнем вопросе, показываем финальный экран
Скрываем блок с вопросами (добавляем класс hidden)
*/
this.quiz.querySelector(".js-quiz-steps").classList.add("hidden");
/* Показываем финальный блок с формой телефона (удаляем класс hidden) */
this.quiz.querySelector(".js-quiz-final").classList.remove("hidden");
}
}
/* МЕТОД: prevStep — переход к предыдущему вопросу */
prevStep(e) {
/* Отменяем стандартное поведение кнопки */
e.preventDefault();
/* Получаем текущий номер шага */
this.activeStep = +this.stepsSlider.dataset.step;
/* Проверяем, не находимся ли мы на первом вопросе */
if (this.activeStep > 1) {
/* Если не на первом — переходим к предыдущему вопросу */
this.activeStep -= 1;
/* Обновляем визуальное отображение */
this.updateStatus();
}
/* Если на первом вопросе, ничего не происходит (кнопка не работает) */
}
/*
МЕТОД: updateStatus — обновляет все визуальные элементы квиза
Вызывается каждый раз при переходе между вопросами
*/
updateStatus() {
/*
Сохраняем текущий номер шага в data-атрибут слайдера
Это используется для отслеживания активного шага
*/
this.stepsSlider.dataset.step = this.activeStep;
/* Обновляем текст в HTML с текущим номером шага */
this.progressStep.innerText = this.activeStep;
/*
Обновляем CSS-переменную --active-step
CSS использует эту переменную для расчёта позиции слайдера (translate)
Например, если --active-step = 2, слайдер сдвинется, чтобы показать второй вопрос
*/
this.stepsSlider.style.setProperty("--active-step", this.activeStep);
/* Обновляем значение progress bar на номер текущего шага */
this.progressBar.value = this.activeStep;
/*
Цикл по всем вопросам:
- Удаляем класс "active" со всех вопросов
- Добавляем класс "active" только к текущему вопросу
Класс "active" используется в CSS для показа/скрытия вопросов (opacity)
*/
for (let step of this.steps) {
/* Удаляем класс active из каждого step */
step.classList.remove("active");
/*
Проверяем, соответствует ли data-num этого step текущему номеру
step.dataset.num — это custom data-атрибут, который устанавливается в init()
*/
if (step.dataset.num == this.activeStep) {
/* Если совпадает, добавляем класс active к этому вопросу */
step.classList.add("active");
}
}
}
/*
МЕТОД: init — инициализация квиза (вызывается один раз при создании объекта)
Подготавливает квиз к работе: добавляет data-атрибуты, устанавливает обработчики событий
*/
init() {
/* Счётчик для нумерации вопросов (начинается с 1, не 0) */
let count = 1;
/* Цикл по всем вопросам */
for (let step of this.steps) {
/* Для первого вопроса добавляем класс "active" (будет видимым при загрузке) */
if (count == 1) {
step.classList.add("active");
/* Устанавливаем в data-атрибут слайдера, что активный шаг = 1 */
this.stepsSlider.dataset.step = "1";
}
/*
Добавляем каждому вопросу custom data-атрибут "data-num"
Это используется в updateStatus() для определения, какой вопрос активен
После присваивания увеличиваем count на 1
*/
step.dataset.num = count++;
}
/* Устанавливаем текущий номер шага в HTML (обычно 1) */
this.progressStep.innerText = this.activeStep;
/* Устанавливаем общее количество шагов в HTML */
this.progressAllSteps.innerText = this.countAllSteps;
/*
Устанавливаем максимальное значение progress bar
max должен быть на 1 больше количества вопросов
Например, если 3 вопроса, то max = 4
*/
this.progressBar.max = this.countAllSteps;
/*
Устанавливаем CSS-переменную --all-steps
CSS использует эту переменную для расчёта ширины слайдера
width: calc(100% * var(--all-steps)) — например, 100% * 3 = 300%
*/
this.stepsSlider.style.setProperty("--all-steps", this.countAllSteps);
/*
ДОБАВЛЯЕМ ОБРАБОТЧИКИ СОБЫТИЙ
Когда пользователь нажимает на кнопку, вызывается соответствующий метод
(e) => this.nextStep(e) — стрелочная функция, которая сохраняет контекст (this)
*/
/* Обработчик клика на кнопку "Вперёд" */
this.next.addEventListener("click", (e) => this.nextStep(e));
/* Обработчик клика на кнопку "Назад" */
this.prev.addEventListener("click", (e) => this.prevStep(e));
/* Обработчик клика на кнопку отправки формы */
this.submit.addEventListener("click", (e) => this.submitForm(e));
}
/*
МЕТОД: submitForm — обработка отправки формы
Собирает данные квиза и проверяет валидность перед отправкой
*/
submitForm(e) {
/* Отменяем стандартное поведение (отправку формы на сервер по умолчанию) */
e.preventDefault();
/*
FormData автоматически собирает все данные формы (радио-кнопки, текст и т.д.)
new FormData(form) создаёт объект с парами ключ-значение
Ключи — это name атрибуты (например, "step1", "step2", "phone")
Значения — это выбранные значения (например, "1", "2", "3")
*/
let data = new FormData(this.quiz.querySelector("form"));
/*
ПЕРВАЯ ВАЛИДАЦИЯ: проверяем, что все поля заполнены
for (const [key, value] of data) — итератор FormData, перебирает все пары ключ-значение
*/
for (const [key, value] of data) {
/*
Если значение пусто или false, это означает, что пользователь не выбрал ответ
или не заполнил поле (например, телефон)
*/
if (!value) {
/* Выводим сообщение об ошибке в консоль браузера */
console.log("Не заполнено поле: " + key);
/* return прерывает выполнение функции (форма не отправляется) */
return;
}
}
/*
ВТОРАЯ ИТЕРАЦИЯ: если все поля заполнены, выводим результаты
Этот цикл выполняется только если прошла первая валидация
*/
for (const [key, value] of data) {
/* Выводим в консоль каждую пару ключ-значение */
console.log(key, value);
}
/*
МЕСТО ДЛЯ ОТПРАВКИ НА СЕРВЕР:
Здесь можно добавить fetch или axios для отправки данных:
fetch('/api/quiz-submit', {
method: 'POST',
body: data
}).then(response => response.json())
.then(result => console.log('Успешно отправлено!', result))
.catch(error => console.error('Ошибка:', error));
*/
}
}
/*
СОЗДАНИЕ ЭКЗЕМПЛЯРА КЛАССА QUIZ
Создаём новый объект Quiz и передаём селектор основного контейнера
".js-quiz" — это класс, который мы использовали в HTML
После этого все методы класса начинают работать
*/
let quiz = new Quiz(".js-quiz");Для удобства можно скачать файлы одним архивом
Скачать ZIP архив с файлами КвизаТакже можно посмотреть работу заготовки на CodePen
Quiz на CodePen