Заготовка под Quiz для сайта. Форма-вопросник

Данный код представляет собой готовую заготовку для создания интерактивного квиза на вашем сайте. Квиз позволяет пользователям последовательно проходить вопросы с вариантами ответов, отслеживать прогресс и завершить форму с контактными данными.

Основные возможности

  • Многошаговая навигация: пользователь переходит между вопросами с помощью кнопок «Prev» и «Next»
  • Визуальное отображение прогресса: прогресс-бар и счётчик текущего шага
  • Плавная анимация: переходы между вопросами происходят с CSS-анимацией
  • Финальная форма: после прохождения всех вопросов пользователь заполняет номер телефона
  • Валидация данных: проверка на заполненность всех полей перед отправкой

Структура файлов

Вам понадобятся три файла:

  1. HTML — структура квиза
  2. CSS — оформление и анимация
  3. 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Кнопка отправки

Советы по использованию

  1. Тестирование: проверьте работу на мобильных устройствах — используйте DevTools
  2. Доступность: убедитесь, что можно навигировать только с клавиатуры
  3. Валидация: добавьте проверку формата телефона перед отправкой
  4. Аналитика: добавьте отслеживание событий (какие ответы выбирают пользователи)
  5. Благодарность: покажите сообщение об успешной отправке

Возможные улучшения

  • Добавить обратный отсчёт времени
  • Сохранять прогресс в localStorage (чтобы пользователь мог вернуться позже)
  • Показать результаты квиза на финальном экране
  • Интеграция с CRM-системой для автоматической обработки контактов
  • Добавить конфетти или другие визуальные эффекты при завершении

Версия: 1.0
Браузеры: работает во всех современных браузерах (IE не поддерживается)


Подробный код для размещения в проекте

HTML разметка самого Квиза

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 стилизация (заготовка) всего блока Квиза

Эта стилизация является базовой. Далее, после внедрения на сайт, нужно стилизацию дорабатывать под общую стилизацию сайта.

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, включающим функционал работы квиза

JavaScript
/* КЛАСС 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