Геймификация давно стала мощным инструментом интернет-маркетинга. Людям нравится играть, пробовать удачу и получать бонусы, даже если речь идёт о небольшой скидке или промокоде. Поэтому колесо фортуны - это не просто развлечение, а эффективный способ вовлечения и сбора заявок.
Предлагаю вашему вниманию готовый код "Колеса Фортуны", который можно встроить прямо в лендинг на Тильде. Данный код работает как полноценный виджет.
Предлагаю вашему вниманию готовый код "Колеса Фортуны", который можно встроить прямо в лендинг на Тильде. Данный код работает как полноценный виджет.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Колесо фортуны — Tilda Embed</title>
<style>
:root{
--accent:#7c3aed; /* фиолетовая кнопка */
--accent-hover:#6d28d9;
--text:#111827;
--muted:#6b7280;
--bg:#ffffff;
--ring:#e5e7eb;
}
*{box-sizing:border-box}
body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,"Helvetica Neue",Arial,"Apple Color Emoji","Segoe UI Emoji";color:var(--text);background:transparent}
.wrap{max-width:920px;margin:0 auto;padding:24px}
.card{background:var(--bg);border:1px solid var(--ring);border-radius:20px;padding:20px;box-shadow:0 8px 20px rgba(17,24,39,.06)}
.grid{display:grid;gap:20px}
@media(min-width:900px){.grid{grid-template-columns:1.1fr .9fr;align-items:center}}
h1{margin:0 0 6px;font-size:clamp(20px,2.4vw,28px)}
p.lead{margin:0 0 14px;color:var(--muted)}
/* Форма */
.form{display:grid;gap:12px;margin-top:4px}
.row{display:grid;gap:12px}
@media(min-width:520px){.row{grid-template-columns:1fr 1fr}}
label{font-size:14px;color:var(--muted);display:block;margin-bottom:6px}
input{width:100%;padding:12px 14px;border:1px solid var(--ring);border-radius:12px;font-size:16px}
input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(124,58,237,.15)}
.actions{display:flex;gap:10px;align-items:center;margin-top:6px}
button{
border:0;border-radius:9999px;padding:12px 18px;font-size:16px;font-weight:600;cursor:pointer
}
#spinBtn{background:var(--accent);color:#fff}
#spinBtn:hover{background:var(--accent-hover)}
#spinBtn:disabled{opacity:.5;cursor:not-allowed}
/* Колесо */
.wheel-wrap{position:relative;display:grid;place-items:center}
/* На мобилке растягиваемся по контейнеру, а не по vw */
canvas{display:block;width:100%;max-width:480px;height:auto;aspect-ratio:1/1;transition:transform 6s cubic-bezier(.17,.67,.15,1.03)}
/* Стрелка (указатель) */
.pointer{position:absolute;top:-6px;left:50%;transform:translateX(-50%);width:0;height:0;border-left:16px solid transparent;border-right:16px solid transparent;border-bottom:24px solid #111827;filter:drop-shadow(0 4px 6px rgba(0,0,0,.25))}
/* Результат */
.modal{position:fixed;inset:0;background:rgba(0,0,0,.45);display:none;align-items:center;justify-content:center;padding:20px;z-index:9999}
.modal.show{display:flex}
.modal-card{background:#fff;border-radius:18px;max-width:520px;width:100%;padding:22px;border:1px solid var(--ring);text-align:center}
.prize{font-size:22px;font-weight:800;margin-top:8px}
.secondary{color:var(--muted);margin:6px 0 0}
.modal .btns{display:flex;gap:10px;justify-content:center;margin-top:16px}
.outline{background:#fff;border:1px solid var(--ring)}
/* Служебные метки */
.note{font-size:12px;color:var(--muted)}
</style>
</head>
<body>
<div class="wrap">
<div class="card grid" id="fortune-app">
<div>
<h1>Розыгрыш: крутите колесо фортуны</h1>
<p class="lead">Заполните имя и телефон — и удача может улыбнуться вам ✨</p>
<form class="form" id="playerForm" onsubmit="return false;">
<div class="row">
<div>
<label for="name">Имя</label>
<input id="name" name="name" type="text" placeholder="Иван" required />
</div>
<div>
<label for="phone">Телефон</label>
<input id="phone" name="phone" type="tel" placeholder="+7 900 000-00-00" required />
</div>
</div>
<div class="actions">
<button id="spinBtn" type="button" disabled>Крутить колесо</button>
<span class="note">Без заполнения данных крутить нельзя</span>
</div>
</form>
</div>
<div class="wheel-wrap">
<div class="pointer" aria-hidden="true"></div>
<canvas id="wheel" width="500" height="500" aria-label="Колесо фортуны"></canvas>
</div>
</div>
</div>
<!-- Модальное окно с результатом -->
<div class="modal" id="resultModal" role="dialog" aria-modal="true">
<div class="modal-card">
<h2 id="congrats">Поздравляем!</h2>
<p class="secondary" id="winnerPhone"></p>
<div class="prize" id="winnerPrize"></div>
<div class="btns">
<button class="outline" id="closeModal">Закрыть</button>
<button id="spinAgain">Крутить ещё раз</button>
</div>
</div>
</div>
<script>
/**********************
* НАСТРОЙКИ РАЗДЕЛА *
**********************/
const PRIZES = [
{ label: 'Подарок 1' },
{ label: 'Подарок 2' },
{ label: 'Подарок 3' },
{ label: 'Подарок 4' },
{ label: 'Подарок 5' },
{ label: 'Подарок 6' }
];
const SEGMENT_COLORS = ['#fde68a','#c7d2fe','#bbf7d0','#fecaca','#e9d5ff','#bae6fd','#fbcfe8','#ddd6fe'];
// Разрешить только один спин в этом браузере
const ONE_SPIN_PER_SESSION = true;
/**********************
* РЕАЛИЗАЦИЯ *
**********************/
const dpr = Math.max(1, Math.min(2, window.devicePixelRatio || 1));
const canvas = document.getElementById('wheel');
const ctx = canvas.getContext('2d');
const spinBtn = document.getElementById('spinBtn');
const nameInput = document.getElementById('name');
const phoneInput = document.getElementById('phone');
const form = document.getElementById('playerForm');
const modal = document.getElementById('resultModal');
const closeModalBtn = document.getElementById('closeModal');
const spinAgainBtn = document.getElementById('spinAgain');
const congrats = document.getElementById('congrats');
const winnerPhone = document.getElementById('winnerPhone');
const winnerPrize = document.getElementById('winnerPrize');
let spinning = false;
let currentRotation = 0; // в градусах
function saveResultToStorage(data){ try{ localStorage.setItem('wheel-result', JSON.stringify(data)); }catch(e){} }
function loadResultFromStorage(){ try{ const raw = localStorage.getItem('wheel-result'); return raw ? JSON.parse(raw) : null; }catch(e){ return null; } }
function normalizePrizes(list){
return list.map((p,i)=>{
if(typeof p === 'string') return { label:p, color: SEGMENT_COLORS[i % SEGMENT_COLORS.length] };
return { label: p.label || `Приз ${i+1}`, color: p.color || SEGMENT_COLORS[i % SEGMENT_COLORS.length] };
});
}
const prizes = normalizePrizes(PRIZES);
// Размер от ширины контейнера, а не окна
function containerSize(){
const parent = canvas.parentElement;
const max = 480;
const w = parent ? parent.clientWidth : window.innerWidth;
return Math.min(max, Math.max(200, w));
}
function resizeCanvas(){
const cssSize = containerSize();
// Внешние CSS размеры для масштабирования
canvas.style.width = cssSize + 'px';
canvas.style.height = cssSize + 'px';
// Физические размеры для ретины
canvas.width = Math.floor(cssSize * dpr);
canvas.height = Math.floor(cssSize * dpr);
drawWheel();
}
function drawWheel(){
const { width, height } = canvas;
const cx = width/2, cy = height/2;
const radius = Math.min(cx, cy) - 10*dpr;
const arc = (2*Math.PI)/prizes.length;
ctx.clearRect(0,0,width,height);
// Тень/фон
ctx.save();
ctx.translate(cx,cy);
ctx.beginPath();
ctx.arc(0,0,radius+8*dpr,0,Math.PI*2);
ctx.fillStyle = '#f3f4f6';
ctx.fill();
ctx.restore();
// Сегменты
for(let i=0;i<prizes.length;i++){
const start = -Math.PI/2 + i*arc; // начинаем сверху (12 часов)
const end = start + arc;
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, radius, start, end);
ctx.closePath();
ctx.fillStyle = prizes[i].color;
ctx.fill();
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 2*dpr;
ctx.stroke();
// Текст по радиусу
ctx.save();
ctx.translate(cx, cy);
ctx.rotate((start + end)/2);
ctx.textAlign = 'right';
ctx.fillStyle = '#111827';
ctx.font = `${14*dpr}px system-ui, -apple-system, Segoe UI, Roboto`;
const text = prizes[i].label;
const textRadius = radius - 16*dpr;
const toDraw = text.length>40 ? text.slice(0,37)+"…" : text;
ctx.fillText(toDraw, textRadius, 5*dpr);
ctx.restore();
}
// Центральная шайба
ctx.beginPath();
ctx.arc(cx, cy, 36*dpr, 0, Math.PI*2);
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.lineWidth = 2*dpr; ctx.strokeStyle = '#e5e7eb'; ctx.stroke();
// Окантовка
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI*2);
ctx.lineWidth = 6*dpr; ctx.strokeStyle = '#e5e7eb'; ctx.stroke();
}
function enableWhenValid(){
const nameOk = nameInput.value.trim().length > 1;
const phoneOk = phoneInput.value.trim().length > 5; // мягкая проверка
const alreadyPlayed = (localStorage.getItem('wheel-spun')==='1') || !!loadResultFromStorage();
spinBtn.disabled = !(nameOk && phoneOk) || (ONE_SPIN_PER_SESSION && alreadyPlayed);
spinBtn.textContent = (ONE_SPIN_PER_SESSION && alreadyPlayed) ? 'Вы уже участвовали' : 'Крутить колесо';
}
function spin(){
if(spinning) return;
if(ONE_SPIN_PER_SESSION && (localStorage.getItem('wheel-spun')==='1' || loadResultFromStorage())){
alert('Вы уже участвовали. Результат сохранён.');
return;
}
spinning = true;
spinBtn.disabled = true;
const arcDeg = 360 / prizes.length;
const randomDeg = Math.random() * 360; // финальный угол в пределах 0..360
// 5–8 полных оборотов + финальный случайный угол
const extraTurns = 5 + Math.floor(Math.random()*4);
const target = extraTurns*360 + randomDeg;
currentRotation += target;
canvas.style.transition = 'transform 6s cubic-bezier(.17,.67,.15,1.03)';
canvas.style.transform = `rotate(${currentRotation}deg)`;
const onEnd = () => {
canvas.removeEventListener('transitionend', onEnd);
canvas.style.transition = 'none';
const final = ((currentRotation % 360) + 360) % 360; // 0..359.999
// Стрелка указывает на центр сектора — смещаем на половину дуги
const index = Math.floor(((final + arcDeg/2) % 360) / arcDeg);
const prize = prizes[index];
showResult(prize.label, final);
try{ localStorage.setItem('wheel-spun','1'); }catch(e){}
spinning = false;
enableWhenValid();
};
canvas.addEventListener('transitionend', onEnd);
}
function showResult(prizeLabel, finalDeg){
const name = nameInput.value.trim();
const phone = phoneInput.value.trim();
// Сохраняем постоянный результат
saveResultToStorage({ name, phone, prize: prizeLabel, deg: finalDeg, ts: Date.now() });
congrats.textContent = `Поздравляем, ${name}!`;
winnerPhone.textContent = phone;
winnerPrize.textContent = prizeLabel;
spinBtn.disabled = true;
spinBtn.textContent = 'Вы уже участвовали';
if(ONE_SPIN_PER_SESSION){
closeModalBtn.style.display = 'none';
spinAgainBtn.style.display = 'none';
} else {
closeModalBtn.style.display = '';
spinAgainBtn.style.display = '';
}
modal.classList.add('show');
}
// События
nameInput.addEventListener('input', enableWhenValid);
phoneInput.addEventListener('input', enableWhenValid);
spinBtn.addEventListener('click', spin);
closeModalBtn.addEventListener('click', ()=>{
if(ONE_SPIN_PER_SESSION && loadResultFromStorage()) return; // нельзя закрыть при зафиксированном результате
modal.classList.remove('show');
});
spinAgainBtn.addEventListener('click', ()=>{
if(ONE_SPIN_PER_SESSION && loadResultFromStorage()) return; // нельзя крутить снова
modal.classList.remove('show');
enableWhenValid();
});
modal.addEventListener('click', (e)=>{
if(ONE_SPIN_PER_SESSION && loadResultFromStorage()) return; // нельзя закрыть кликом по фону
if(e.target===modal) modal.classList.remove('show');
});
// Инициализация
resizeCanvas();
// Подписка на изменения размера контейнера — стабильнее на мобилке и в Tilda
window.addEventListener('resize', ()=> requestAnimationFrame(resizeCanvas));
if(window.ResizeObserver){ new ResizeObserver(()=> resizeCanvas()).observe(canvas.parentElement); }
drawWheel();
// Если результат уже есть — восстановим его и покажем экран выигрыша
(function restorePersistentResult(){
const stored = loadResultFromStorage();
if(!stored) { enableWhenValid(); return; }
// Зафиксируем угол
canvas.style.transition = 'none';
const deg = typeof stored.deg === 'number' ? stored.deg : 0;
currentRotation = deg;
canvas.style.transform = `rotate(${deg}deg)`;
congrats.textContent = `Поздравляем, ${stored.name || ''}!`;
winnerPhone.textContent = stored.phone || '';
winnerPrize.textContent = stored.prize || '';
spinBtn.disabled = true;
spinBtn.textContent = 'Вы уже участвовали';
closeModalBtn.style.display = 'none';
spinAgainBtn.style.display = 'none';
modal.classList.add('show');
})();
enableWhenValid();
</script>
</body>
</html>
В коде красным шрифтом выделил ту часть, где можно добавить названия своих призов.
Как работает "Колесо Форуны"? Для того, чтобы запустить колесо пользователю необходимо ввести своё имя и номер телефона. Как только данные заполнены, кнопка "Крутить колесо" становится активной. После того, как пользователь нажал на кнопку колесо плавно раскручивается и через несколько секунд выпадает случайный приз. Один участник может сделать только одну попытку!
Данный виджет адаптирован как под мобильные устройства, так и под десктопы. Дизайн подстраивается под экран, а интерфейс остаётся удобным и интуитивным.
Так геймификация превращает обычный сбор заявок в азартное и вовлекающее взаимодействие. Такое решение позволяет собрать контакты, повысить конверсию и выделиться на фоне стандартных форм "оставьте телефон и получите скидку".
Так геймификация превращает обычный сбор заявок в азартное и вовлекающее взаимодействие. Такое решение позволяет собрать контакты, повысить конверсию и выделиться на фоне стандартных форм "оставьте телефон и получите скидку".
Важно! Если вы планируете использовать собранные контактные данные для маркетинговых активностей, вам необходимо в обязательном порядке добавить на лендинг галочку с согласием на обработку персональных данных! Так требует закон.
Главное — что вам не нужно разбираться в коде с нуля. Я уже всё сделал и адаптировал под Тильду.
Код можно встроить в любой Zero Block через HTML-блок или в блок T123, и он сразу будет работать.
Берите, внедряйте на свои лендинги, тестируйте механику и смотрите, как реагируют ваши клиенты.
Геймификация — это про эмоции и вовлечённость. А если у вас под рукой готовый инструмент, почему бы не попробовать прямо сейчас?
Код можно встроить в любой Zero Block через HTML-блок или в блок T123, и он сразу будет работать.
Берите, внедряйте на свои лендинги, тестируйте механику и смотрите, как реагируют ваши клиенты.
Геймификация — это про эмоции и вовлечённость. А если у вас под рукой готовый инструмент, почему бы не попробовать прямо сейчас?