Перейти к основному содержимому
Altcraft Docs LogoAltcraft Docs Logo
Для пользователяДля разработчикаДля администратора
Веб-сайтБаза знаний
Русский
  • Русский
  • English
v75
Войти
  • Документация пользователя
  • FAQ
  • Термины
  • Обновления платформы
  • Хранение и сбор данных
  • Каналы коммуникации
  • Сегментация
  • Шаблоны сообщений
  • Рассылки
  • Кампании
  • Сценарии автоматизации
  • Маркет
  • Лояльность
  • Веб-слой
    • Формы
    • Пиксели
    • Попапы
      • Создание и публикация попапа
      • Настройка попапа в редакторе кода
      • Управление попапами вручную через скрипт
      • Аналитика попапов
      • Руководство: попап для подписки на push
      • Базовые кейсы размещения попапа через Менеджер тегов
      • Кейс: Создание попапа с виджетом "Колесо фортуны"
    • Менеджер тегов
  • Отчеты и аналитика
  • Интеграции
  • Настройки
  • API-запросы: с чего начать
  • Архив документации
  • Библиотека email-маркетолога
  • Веб-слой
  • Попапы
  • Кейс: Создание попапа с виджетом "Колесо фортуны"
Документация для версии v75

Кейс: Создание попапа с виджетом "Колесо фортуны"

Специальный виджет "Колесо фортуны" позволяет создать интерактивный попап, который вовлекает посетителей сайта в игру с призами. Пользователь заполняет простую форму и вращает колесо, чтобы получить случайный подарок: скидку, промокод или другой бонус. Данное руководство описывает процесс создания такого попапа в конструкторе Altcraft:


Перед настройкой колеса рекомендуется создать поле в базе профилей, в которое будет сохраняться информация о выигранном призе. О том, как создавать дополнительные поля, вы можете узнать в этой статье.

Создание попапа и выбор шаблона​

Начните с создания нового попапа в разделе Веб-слой — Попапы, нажав кнопку Создать. Выберите тип попапа. Для колеса фортуны идеально подходит Модальное окно — оно появляется в центре экрана:


Выберите шаблон. Воспользуйтесь любым из готовых шаблонов как основой. Для упрощения кастомизации рекомендуется использовать шаблоны с минималистичным дизайном (например, с космонавтом):


Задайте имя и описание. Укажите системное имя и при необходимости — описание:


Интеграция кода виджета​

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

  • CSS
  • HTML
  • JavaScript

Перейдите на вкладку CSS. Не удаляйте существующие стили. Прокрутите редактор в самый конец и добавьте предоставленный CSS-код для стилизации колеса и его контейнера.

.formWrapper {
display: flex;
align-items: center;
justify-content: space-around;
flex-wrap: wrap;
}

.ac-button:disabled {
opacity: 0.6;
cursor: not-allowed;
background-color: #cccccc;
color: #666666;
transform: none;
box-shadow: none;
}

.ac-button:disabled:hover {
background-color: #cccccc;
color: #666666;
transform: none;
box-shadow: none;
}

В разделе "Настройки" откройте вкладку HTML. Полностью удалите текущее содержимое редактора и вставьте предоставленный HTML-код для колеса.

<div class="ac-popup-overlay">
<div class="ac-modal">
<div class="ac-modal_content">
<div style="display: none;" data-popupId="{{popup('copywriting' 'popupId' 'text')}}"></div>
<div id="firstPage">
<h4 class="ac-title">
{{popup('copywriting' 'title' 'text')}}
</h4>
<div class="ac-description">
{{popup('copywriting' 'text' 'text')}}
</div>
<div class="formWrapper">
<div class="canvas-container">
<canvas id="canvas" width="420" height="420"></canvas>
</div>
<form id="ac-form" class="ac-form" action>
<div class="ac-field">
<label class="ac-label" for="email">
{{popup('copywriting' 'emailLabel' 'text')}}
</label>
<input
id="email"
type="email"
class="ac-input"
name="{{popup('form' 'email' 'fieldData')}}"
placeholder="{{popup('copywriting' 'emailPlaceholder' 'text')}}"
/>
</div>
<div class="ac-field">
<label class="ac-label" for="name">
{{popup('copywriting' 'nameLabel' 'text')}}
</label>
<input
id="name"
class="ac-input"
name="{{popup('form' 'name' 'fieldData')}}"
placeholder="{{popup('copywriting' 'namePlaceholder' 'text')}}"
/>
</div>
<div class="ac-field">
<label>
<input type="checkbox" name="{{popup('form' 'privacyPolicy' 'fieldData')}}" id="privacyPolicy" required>
<span>С <a href="{{popup('copywriting' 'policy' 'link')}}" target="_blank">политикой конфиденциальности</a> ознакомлен</span>
</label>
</div>
<div class="ac-validation-msg" for="email"></div>
<input type="text" style="display: none;" class="ac-input" id="prizeField" name="{{popup('form' 'prize' 'fieldData')}}">
<button class="ac-button" type="button" id="winBtn" disabled>
{{popup('copywriting' 'winBtn' 'text')}}
</button>

<div class="ac-form_submit" style="display: none;">
<button type="submit" class="ac-button" id="submitBtn">
{{popup('cta' 'text' 'text')}}
</button>
</div>
</form>
</div>
</div>
<div id="secondPage" style="display: none;">
<h4 class="ac-title">
{{popup('copywriting' 'title-congrats' 'text')}}
</h4>
<div class="ac-description">
{{popup('copywriting' 'text-before-prize' 'text')}}
<span id="prizeName"></span>
<div>
{{popup('copywriting' 'text-after-prize' 'text')}}
</div>
</div>
</div>
</div>
{{popup('closeButton' 'closeButton' 'closeBtn')}}
</div>
</div>

Переключитесь на вкладку JavaScript. Полностью удалите текущее содержимое и вставьте предоставленный JS-код, который отвечает за логику вращения колеса, определение выигрыша и работу с формой.

// Список выигрышей. Длина может варьироваться
// id - Идентификатор, отправляемый на сервер
// text - Подпись у сектора колеса
const options = [
{id: 1, text: '$100'},
{id: 2, text: '$10'},
{id: 3, text: '$25'},
{id: 4, text: '$250'},
{id: 5, text: '$30'},
{id: 6, text: '$1000'},
{id: 7, text: '$1'},
{id: 8, text: '$200'},
{id: 9, text: '$45'},
{id: 10, text: '$500'},
{id: 11, text: '$5'},
{id: 12, text: '$20'}
]

// Цвета секторов колеса. Если цветов меньше, чем секторов, то цвета повторяются циклично
const colors = ['white', '#3583FF']

// Начальный угол поворота колеса
let currentAngle = 0;

// Длительность вращения колеса задаётся случайно
// Минимальное время вращения (сек)
const minSpinTime = 4;

// Максимальное время вращения (сек)
const maxSpinTime = 7;

// Задержка между остановкой колеса и сообщением о выигрыше (сек)
const pageSwitchDelay = 2;

// Цвет подписей секторов на колесе
const textColor = "black";


let isPreview = true;
let myPopup = null;

const allPopups = document.querySelectorAll('[data-ac-popup-container]');

allPopups.forEach(item =>{
const idContainer = item.shadowRoot.querySelector('[data-popupId={{popup('copywriting' 'popupId' 'text')}}]');
if(idContainer){
myPopup = item.shadowRoot;
isPreview = false;
}
})

if (!myPopup) {
myPopup = document;
}

const winBtn = myPopup.getElementById('winBtn');

function initInputs(){
let emailInput = myPopup.getElementById('email');
let nameInput = myPopup.getElementById('name');
let policyInput = myPopup.getElementById('privacyPolicy');

function updateButtonState() {
const isEmailEmpty = !emailInput.value.trim();
const isNameEmpty = !nameInput.value.trim();
const isPolicyChecked = policyInput.checked;

console.log(myPopup.getElementById('winBtn'))
myPopup.getElementById('winBtn').disabled = isEmailEmpty || isNameEmpty || !isPolicyChecked
}

emailInput.addEventListener('input', updateButtonState);
nameInput.addEventListener('input', updateButtonState);
policyInput.addEventListener('input', updateButtonState);
myPopup.getElementById("winBtn").addEventListener("click", spin);

updateButtonState();
}
if(isPreview) {
setTimeout(()=>{initInputs()}, 1000);
} else {
initInputs();
}


let isInitialized = false;

function initializeWheel() {
if (isInitialized) return;

const canvas = myPopup.getElementById("canvas");
if (!canvas) return;

const ctx = canvas.getContext("2d");
if (ctx && !canvas.dataset.initialized) {
drawRouletteWheel();
canvas.dataset.initialized = "true";
isInitialized = true;
}
}

const observer = new MutationObserver((mutations) => {
for (let mutation of mutations) {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1 && (node.id === 'canvas' || node.querySelector?.('#canvas'))) {
setTimeout(initializeWheel, 100);
}
});
}
}
});

if (document.body) {
observer.observe(document.body, { childList: true, subtree: true });
}

document.addEventListener('DOMContentLoaded', initializeWheel);

let segmentAngle = Math.PI / (options.length / 2);
let spinTimeout = null;

let spinAngleStart = 0;

let spinTime = 0;
let spinTimeTotal = 0;

let ctx;



function drawRouletteWheel() {
const canvas = myPopup.getElementById("canvas");

if (canvas.getContext) {
const centerX = 210;
const centerY = 210;
const outsideRadius = 200;
const textRadius = 130;
const insideRadius = 0;

ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, 400, 400);

ctx.strokeStyle = "black";
ctx.lineWidth = 2;
ctx.font = 'bold 12px Helvetica, Arial';

ctx.strokeStyle = "white";
ctx.lineWidth = 15;
ctx.beginPath();
ctx.arc(centerX, centerY, outsideRadius, 0, 2 * Math.PI);
ctx.stroke();

ctx.strokeStyle = "black";
ctx.lineWidth = 2;

for(let i = 0; i < options.length; i++) {
const angle = currentAngle + i * segmentAngle;
const segmentMiddleAngle = angle + segmentAngle / 2;
ctx.fillStyle = colors[i % colors.length];
ctx.beginPath();
ctx.arc(centerX, centerY, outsideRadius, angle, angle + segmentAngle, false);
ctx.arc(centerX, centerY, insideRadius, angle + segmentAngle, angle, true);
ctx.stroke();
ctx.fill();

ctx.save();
ctx.fillStyle = textColor;

const textX = centerX + Math.cos(segmentMiddleAngle) * textRadius;
const textY = centerY + Math.sin(segmentMiddleAngle) * textRadius;

ctx.translate(textX, textY);
ctx.rotate(segmentMiddleAngle);

const text = options[i].text;
ctx.fillText(text, -ctx.measureText(text).width / 2, 0);
ctx.restore();
}

ctx.fillStyle = "white";
ctx.strokeStyle = "#666666";
ctx.lineWidth = 2;

ctx.beginPath();
ctx.moveTo(centerX - 10, centerY - 15);
ctx.lineTo(centerX + 30, centerY);
ctx.lineTo(centerX - 10, centerY + 15);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
}

function rotateWheel() {
spinTime += 30;
if(spinTime >= spinTimeTotal) {
stopRotateWheel();
return;
}
const spinAngle = spinAngleStart - easeOut(spinTime, 0, spinAngleStart, spinTimeTotal);
currentAngle += (spinAngle * Math.PI / 180);
drawRouletteWheel();
spinTimeout = setTimeout(rotateWheel, 30);
}

function spin(e) {
e.preventDefault();
const form = myPopup.getElementById('ac-form');
const isValid = form.checkValidity();

if(!isValid){
myPopup.getElementById('submitBtn').click();
return;
}
winBtn.removeEventListener('click', spin);
spinAngleStart = Math.random() * 10 + 10;
spinTime = 0;
spinTimeTotal = Math.random() * (maxSpinTime - minSpinTime) + minSpinTime * 1000;
rotateWheel();
}

function handlePrize(prize){
myPopup.getElementById("prizeField").value = prize.id;
myPopup.getElementById('submitBtn').click();
myPopup.getElementById('prizeName').innerText = prize.text;
}

function showSecondPage() {
myPopup.getElementById('firstPage').style.display = 'none';
myPopup.getElementById('secondPage').style.display = 'block';
}

function stopRotateWheel() {
clearTimeout(spinTimeout);

const degrees = currentAngle * 180 / Math.PI;
const segmentDegrees = segmentAngle * 180 / Math.PI;
const index = Math.floor((360 - degrees % 360) / segmentDegrees);

handlePrize(options[index]);

const form = myPopup.getElementById('ac-form');
const isValid = form.checkValidity();

setTimeout(()=>{
showSecondPage();
}, pageSwitchDelay * 1000);

}

function easeOut(currentTime, startValue, changeInValue, duration) {
const normalizedTime = currentTime / duration;
const squaredTime = normalizedTime * normalizedTime;
const cubedTime = squaredTime * normalizedTime;

return startValue + changeInValue * (cubedTime + -3 * squaredTime + 3 * normalizedTime);
}

drawRouletteWheel();

myPopup.getElementById('ac-form').submit = () => {}

Настройка контента и дизайна в визуальном редакторе​

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

  • Ширина окна
  • Текстовое наполнение
  • Внешний вид

Первым делом задайте ширину окна (Форма — width). Размеры холста с колесом фиксированы (420 px). Поэтому ширина всего попапа должна быть не менее 420 px. Если вы хотите разместить колесо и форму для ввода данных на одной горизонтальной линии, установите ширину не менее 810 px.

На вкладке Копирайтинг заполните ключевые текстовые параметры:

  • title — главный заголовок на первом экране (например, "Испытай удачу!").
  • text — поясняющий текст или описание акции.
  • emailLabel и emailPlaceholder — подпись и подсказка для поля ввода email.
  • nameLabel и namePlaceholder — подпись и подсказка для поля ввода имени.
  • policy — обязательная ссылка на страницу с политикой конфиденциальности. Без заполнения этого поля сохранить попап не получится.
  • winBtn — текст на кнопке, которая запускает вращение колеса (например, "Крутить колесо!").
  • popupId — обязательный параметр. Уникальный текстовый идентификатор попапа (например, bookverse_wheel). Необходим для корректной работы, если на странице используется несколько попапов.

Попап состоит из двух экранов. Параметры для экрана с сообщением о выигрыше:

  • title-congrats — заголовок поздравления.
  • text-before-prize — текст, который будет показан перед названием выигранного приза.
  • text-after-prize — дополнительный текст под сообщением о призе.

Используйте остальные вкладки раздела Дизайн для тонкой настройки внешнего вида:

  • Фон — измените цвет фона попапа и прозрачность затемнения страницы (overlay).
  • Рамка — настройте скругление углов всплывающего окна.
  • Форма — задайте стили для полей ввода: отступы, цвета границ, фона и текста.
  • Действия — настройте внешний вид кнопок (цвет, скругление, шрифт).
  • Типографика — выберите общий шрифт и стили для всех текстовых элементов.

Любые изменения в визуальном редакторе сразу отображаются в окне предпросмотра справа.

Кастомизация колеса фортуны​

Внешний вид и поведение самого колеса (призы, цвета секторов, скорость вращения) настраиваются путём правки переменных в JavaScript-коде.

Вернитесь в раздел Настройки — JavaScript. В начале файла найдите и отредактируйте следующие переменные.

  • Призы (options)
  • Цвета секторов (colors)
  • Скорость вращения
  • Задержка показа
  • Цвет текста (textColor)

Переменная options определяет список призов на колесе. Это массив объектов, где каждый объект содержит id и текст приза text.

const options = [
{ id: 1, text: '$100' },
{ id: 2, text: '$10' },
{ id: 3, text: '$25' },
{ id: 4, text: '$250' },
{ id: 5, text: '$30' },
{ id: 6, text: '$1000' },
{ id: 7, text: '$1' },
{ id: 8, text: '$200' },
{ id: 9, text: '$45' },
{ id: 10, text: '$500' },
{ id: 11, text: '$5' },
{ id: 12, text: '$20' }
];

Как изменить:

  • Текст приза: Замените значение text на нужное (например, '10% скидка' или 'Бесплатная доставка').
  • Количество призов: Добавьте новые объекты в массив или удалите существующие. Количество секторов на колесе изменится автоматически.

Переменная colors — это массив цветов для заливки секторов колеса. Цвета применяются по порядку, начиная с первого сектора.

const colors = ['black', 'orange'];

Как изменить: Замените цвета в массиве на нужные. Вы можете использовать:

  • Названия цветов на английском: 'red', 'blue', 'green'
  • HEX-коды: '#4f46e5', '#f59e0b', '#10b981'
  • RGB/RGBA значения: 'rgb(79, 70, 229)'

Длительность вращения колеса задаётся двумя переменными:

  • minSpinTime — минимальное время вращения (в секундах).
  • maxSpinTime — максимальное время вращения (в секундах).

Фактическое время каждого вращения будет случайным числом в этом диапазоне.

const minSpinTime = 4;
const maxSpinTime = 7;

Как изменить: Увеличьте значения, чтобы колесо вращалось дольше, или уменьшите значения для более быстрого результата.

Переменная pageSwitchDelay задаёт паузу (в секундах) между остановкой колеса и автоматическим переходом на экран с сообщением о выигрыше.

const pageSwitchDelay = 2;

Как изменить: Измените значение на необходимое количество секунд.

Переменная textColor определяет цвет надписей с названиями призов на секторах колеса.

const textColor = "white";

Как изменить: Замените значение на любой валидный CSS-цвет, чтобы текст хорошо читался на фоне сектора. Например, "#ffffff", "black", "#1f2937".

Сохранение данных о выигранном призе​

После того как пользователь вращает колесо и выигрывает приз, информация о выигрыше автоматически заполняется в скрытом поле формы prize. Чтобы сохранить эти данные в профиле пользователя в Altcraft, необходимо настроить действие импорта в разделе Действия.

В редакторе попапа откройте вкладку Действия:

Настройте импорт профилей:

  • Следуйте стандартной процедуре настройки импорта, описанной в статье о создании попапов.
  • В настройках импорта обязательно настройте сопоставление поля prize из формы с нужным полем в вашей базе данных Altcraft.

По умолчанию JavaScript-код записывает в поле prize текст выигранного приза (prize.text). Если вам нужно сохранять идентификатор приза (prize.id), отредактируйте функцию handlePrize() в JS-коде, заменив prize.text на prize.id.

После настройки импорта при каждой отправке формы данные о выигрыше (текст или ID приза) будут сохраняться в профиле пользователя вместе с его email и именем.

Публикация попапа​

После полной настройки сохраните попап и убедитесь, что его статус Активен.

В разделе Появление привяжите попап к контейнеру Менеджера тегов:

Задайте Имя тега и настройте триггер — условие, при котором попап будет показан (например, Таймер или Глубина прокрутки):

Подробнее о настройке триггеров читайте в отдельной статье.

После полной настройки сохраните попап и убедитесь, что его статус установлен в Активен. После публикации попап с колесом фортуны будет готов к работе и начнёт появляться у посетителей вашего сайта согласно заданным правилам.

Автоматическая отправка персонализированных призов через сценарии​

Для автоматической отправки промокодов, соответствующих выигранным призам, настройте сценарий с триггером на изменение поля Prize. Вам потребуется создать отдельные промокоды для каждого типа приза и настроить условные ветвления в сценарии.

Создание промокодов​

Перейдите в Лояльность — Создать для создания новой программы лояльности. В настройках программы создайте промокоды для каждого выигрыша. Например:

  • Скидка 10% — промокод BOOK10
  • Скидка 15% — промокод BOOK15
  • Бесплатная доставка — промокод FREESHIP
  • И т. д. для каждого приза

Подробнее о промокодах можно узнать в этой статье.

Создание шаблона письма​

Создайте универсальный шаблон письма, который будет показывать разный текст в зависимости от выигранного приза. Используйте логические выражения для проверки значения поля Prize. Подробнее о работе с логическими выражениями и переменными в шаблонах можно узнать в этой статье.

В шаблоне проверяется, какое значение содержит поле с призом (например Prize) в профиле пользователя ({lead.Prize}), и в зависимости от этого показывается соответствующий текст. Для каждого типа приза в текст письма нужно вставить переменную промокода:


{if lead.Prize}

{if lead.Prize equal "1" or lead.Prize equal "$100"}
<p>Поздравляем, {lead._fname}!</p>
<p>Вы выиграли денежный приз в размере $100!</p>
<p>Ваш промокод: {loyalty.prize100.promocode}</p>
<p>Промокод действует на все товары в нашем магазине.</p>
{end}

{if lead.Prize equal "2" or lead.Prize equal "$10"}
<p>Поздравляем, {lead._fname}!</p>
<p>Вы выиграли денежный приз в размере $10!</p>
<p>Ваш промокод: {loyalty.prize10.promocode}</p>
<p>Промокод действует на все товары в нашем магазине.</p>
{end}

{if lead.Prize equal "3" or lead.Prize equal "скидка 10%"}
<p>Поздравляем, {lead._fname}!</p>
<p>Вы выиграли скидку 10% на следующую покупку!</p>
<p>Ваш промокод: {loyalty.discount10.promocode}</p>
<p>Скидка действует на все товары в нашем каталоге.</p>
{end}
{else}
<p>Поздравляем, {lead._fname}!</p>
<p>Вы выиграли приз в нашей акции!</p>
<p>Подробности уточняйте у менеджера.</p>
{end}

В результате у вас получится шаблон, где для каждого условия указан свой промокод:

Создание сценария​

Перейдите в раздел Сценарии — Создать. Выберите базу данных, в которой хранятся профили участников акции. В настройках входа установите опцию Отключить повторный вход в сценарий для профиля клиента. Это гарантирует, что сценарий будет запущен для каждого пользователя только один раз при каждом новом выигрыше.

После создания сценария добавьте триггер входа. Выберите тип триггера Изменение профиля, укажите поле Prize и установите оператор Изменено. Это означает, что сценарий будет запускаться каждый раз, когда в профиле пользователя появляется или изменяется значение поля с призом.

После настройки триггера добавьте в сценарий элемент Условие. В условии проверяйте значение поля Prize. Например, укажите Поле — Prize, Оператор — equal, и введите значение 1 (если в поле сохраняется ID приза) или текст первого приза, например $100.

Если условие выполняется (пользователь выиграл приз с ID 1 или "$100"), добавьте два узла:

  • Промокод — выберите созданную программу лояльности и конкретный промокод, соответствующий этому призу.
  • Отправить email — выберите созданный универсальный шаблон письма.

Если условие не выполняется, добавьте следующий элемент Условие для проверки второго приза. Настройте его аналогично: проверяйте, содержит ли поле Prize значение 2 или текст второго приза.

Повторите эту логику для каждого приза, создав цепочку условий и соответствующих действий по назначению промокодов и отправке писем:

В конце не забудьте сохранить и активировать сценарий.

Последнее обновление 2 мар. 2026 г.
Предыдущая страница
Базовые кейсы размещения попапа через Менеджер тегов
Следующая страница
Менеджер тегов
  • Создание попапа и выбор шаблона
    • Интеграция кода виджета
    • Настройка контента и дизайна в визуальном редакторе
    • Кастомизация колеса фортуны
    • Сохранение данных о выигранном призе
  • Публикация попапа
  • Автоматическая отправка персонализированных призов через сценарии
    • Создание промокодов
    • Создание шаблона письма
    • Создание сценария
© 2015 - 2026 Altcraft. Все права защищены.