Разработка квиза (Quiz) с расчётом результата на сайте
Квиз — маркетинговый инструмент, который генерирует лиды через вовлечение. Правильно построенный квиз собирает контакты, сегментирует аудиторию и рекомендует продукт. Неправильный — просто набор вопросов без ценности для пользователя.
Типы квизов
Типологический — каждый ответ прибавляет очки к одному или нескольким «профилям». В конце побеждает профиль с максимальным счётом. Используется для «Какой вы тип клиента», «Подберём тариф под вас».
Балльный — ответы дают числовые очки, итоговый результат определяется диапазоном. Типично для тестов знаний, оценки уровня.
Ветвящийся (branching) — следующий вопрос зависит от ответа на предыдущий. Позволяет строить сложные сценарии с разными итогами.
Рекомендательный — квиз собирает параметры и на выходе рекомендует конкретный продукт, тариф, специалиста.
Структура данных
interface QuizQuestion {
id: string;
text: string;
type: 'single' | 'multiple' | 'scale';
answers: QuizAnswer[];
nextQuestion?: string | ((answers: Record<string, string[]>) => string); // для branching
}
interface QuizAnswer {
id: string;
text: string;
scores: Record<string, number>; // { profile_a: 3, profile_b: 1 }
image?: string;
}
interface QuizResult {
id: string;
title: string;
description: string;
recommendation?: string;
cta?: { text: string; url: string };
minScore?: number; // для балльного типа
maxScore?: number;
}
Движок расчёта
class QuizEngine {
constructor(questions, results) {
this.questions = questions;
this.results = results;
this.answers = {}; // questionId -> answerId[]
this.scores = {};
}
answer(questionId, answerIds) {
this.answers[questionId] = answerIds;
const question = this.questions.find(q => q.id === questionId);
for (const answerId of answerIds) {
const answer = question.answers.find(a => a.id === answerId);
if (!answer?.scores) continue;
for (const [profile, score] of Object.entries(answer.scores)) {
this.scores[profile] = (this.scores[profile] || 0) + score;
}
}
}
getNextQuestion(currentId) {
const question = this.questions.find(q => q.id === currentId);
if (typeof question.nextQuestion === 'function') {
return question.nextQuestion(this.answers);
}
return question.nextQuestion;
}
calculateResult() {
// Типологический: победитель по очкам
const [topProfile] = Object.entries(this.scores)
.sort(([, a], [, b]) => b - a);
return this.results.find(r => r.id === topProfile?.[0]) ?? this.results[0];
}
getTotalScore() {
return Object.values(this.scores).reduce((s, v) => s + v, 0);
}
}
React-компонент квиза
function Quiz({ config }) {
const engine = useRef(new QuizEngine(config.questions, config.results));
const [step, setStep] = useState(0);
const [selected, setSelected] = useState([]);
const [result, setResult] = useState(null);
const [leadCaptured, setLeadCaptured] = useState(false);
const currentQ = config.questions[step];
const progress = ((step / config.questions.length) * 100).toFixed(0);
function handleNext() {
engine.current.answer(currentQ.id, selected);
setSelected([]);
if (step + 1 >= config.questions.length) {
setResult(engine.current.calculateResult());
} else {
setStep(s => s + 1);
}
}
if (result && !leadCaptured) {
return <LeadCapture onSubmit={(contact) => {
submitLead({ contact, result, answers: engine.current.answers });
setLeadCaptured(true);
}} />;
}
if (result && leadCaptured) {
return <QuizResult result={result} score={engine.current.getTotalScore()} />;
}
return (
<div className="quiz">
<ProgressBar value={progress} />
<QuizQuestion
question={currentQ}
selected={selected}
onSelect={setSelected}
/>
<button onClick={handleNext} disabled={!selected.length}>
{step + 1 < config.questions.length ? 'Далее' : 'Узнать результат'}
</button>
</div>
);
}
Захват лида перед результатом
Классическая механика: пользователь проходит все вопросы, но результат видит только после ввода email (или телефона). Конверсия выше, чем у обычной формы подписки, потому что человек уже вложил время.
function LeadCapture({ onSubmit }) {
const { register, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h3>Ваш результат готов!</h3>
<p>Укажите email, чтобы получить персональные рекомендации</p>
<input {...register('email', { required: true })} type="email" placeholder="[email protected]" />
<input {...register('name')} placeholder="Имя (необязательно)" />
<button type="submit">Показать результат</button>
</form>
);
}
Аналитика
Для квизов важно отслеживать не только финальные конверсии, но и dropout на каждом шаге:
// GTM / GA4
function trackStep(questionIndex, questionId) {
window.dataLayer?.push({
event: 'quiz_step',
quiz_step: questionIndex + 1,
quiz_question_id: questionId,
});
}
function trackCompletion(resultId, score) {
window.dataLayer?.push({
event: 'quiz_complete',
quiz_result: resultId,
quiz_score: score,
});
}
Анимация переходов
Переходы между вопросами улучшают ощущение от продукта. Простая анимация через CSS:
.quiz-question {
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from { opacity: 0; transform: translateX(24px); }
to { opacity: 1; transform: translateX(0); }
}
Для React — framer-motion с AnimatePresence для плавного удаления предыдущего вопроса.
Сроки
Простой балльный квиз на 5–10 вопросов с захватом лида и результатом — 3–4 рабочих дня. Ветвящийся квиз с несколькими профилями, анимацией, интеграцией в CRM и A/B-тестированием конфигураций — 8–12 дней.







