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

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

Специальный виджет "Колесо фортуны" позволяет создать интерактивный попап, который вовлекает посетителей сайта в игру с призами. Пользователь заполняет простую форму и вращает колесо, чтобы получить случайный подарок: скидку, промокод или другой бонус. Данное руководство описывает процесс создания такого попапа в конструкторе 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">
<canvas id="canvas" width="420" height="420"></canvas>
<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-код, который отвечает за логику вращения колеса, определение выигрыша и работу с формой.

function initWheel() {
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 = ['black', 'orange'];
let currentAngle = 0;
const minSpinTime = 4;
const maxSpinTime = 7;
const pageSwitchDelay = 2;
const textColor = "white";

let segmentAngle = Math.PI / (options.length / 2);
let spinTimeout = null;
let spinAngleStart = 0;
let spinTime = 0;
let spinTimeTotal = 0;
let ctx = null;

const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.addedNodes.length || mutation.type === 'childList') {
initializeWheelIfPopupFound();
}
});
});

observer.observe(document.body, {
childList: true,
subtree: true,
attributes: false,
characterData: false
});

setTimeout(initializeWheelIfPopupFound, 100);

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

allPopups.forEach(item => {
try {
if (item.dataset.wheelInitialized === 'true') {
return;
}

const popupId = "{{popup('copywriting' 'popupId' 'text')}}";
const shadowRoot = item.shadowRoot;

if (!shadowRoot) {
return;
}

const idContainer = shadowRoot.querySelector(`[data-popupId="${popupId}"]`);

if (idContainer) {
item.dataset.wheelInitialized = 'true';
initWheelComponents(shadowRoot);
observer.disconnect();
}
} catch (e) {
console.log('Error accessing popup:', e);
}
});
}

function initWheelComponents(myPopup) {
try {
const winBtn = myPopup.getElementById('winBtn');
const canvas = myPopup.getElementById('canvas');
const emailInput = myPopup.getElementById('email');
const nameInput = myPopup.getElementById('name');
const policyInput = myPopup.getElementById('privacyPolicy');
const form = myPopup.getElementById('ac-form');

if (!winBtn || !canvas || !form) {
return;
}

form.submit = () => {};

if (canvas.getContext) {
ctx = canvas.getContext("2d");
drawRouletteWheel();
}

winBtn.addEventListener("click", spin);

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

if (winBtn) {
winBtn.disabled = isEmailEmpty || isNameEmpty || !isPolicyChecked;
}
}

if (emailInput) emailInput.addEventListener('input', updateButtonState);
if (nameInput) nameInput.addEventListener('input', updateButtonState);
if (policyInput) policyInput.addEventListener('input', updateButtonState);

updateButtonState();

if (form) {
form.addEventListener('submit', function(e) {
e.preventDefault();
return false;
});
}

} catch (error) {
console.error('Error in initWheelComponents:', error);
}
}

function drawRouletteWheel() {
const allPopups = document.querySelectorAll('[data-ac-popup-container]');
let canvas = null;
let myPopup = null;

allPopups.forEach(item => {
if (item.dataset.wheelInitialized === 'true') {
try {
canvas = item.shadowRoot.getElementById('canvas');
myPopup = item.shadowRoot;
} catch (e) {}
}
});

if (!canvas || !canvas.getContext || !myPopup) {
return;
}

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) {
if (e) e.preventDefault();

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

allPopups.forEach(item => {
if (item.dataset.wheelInitialized === 'true') {
try {
winBtn = item.shadowRoot.getElementById('winBtn');
} catch (e) {}
}
});

if (!winBtn) return;

winBtn.disabled = true;
winBtn.removeEventListener('click', spin);

spinAngleStart = Math.random() * 10 + 10;
spinTime = 0;
spinTimeTotal = Math.random() * (maxSpinTime - minSpinTime) + minSpinTime * 1000;

rotateWheel();
}

function handlePrize(prize) {
const allPopups = document.querySelectorAll('[data-ac-popup-container]');
let myPopup = null;

allPopups.forEach(item => {
if (item.dataset.wheelInitialized === 'true') {
try {
myPopup = item.shadowRoot;
} catch (e) {}
}
});

if (!myPopup) return;

const prizeField = myPopup.getElementById('prizeField');
if (prizeField) {
prizeField.value = prize.text;
}

const prizeNameElement = myPopup.getElementById('prizeName');
if (prizeNameElement) {
prizeNameElement.innerText = prize.text;
}

const submitBtn = myPopup.getElementById('submitBtn');
if (submitBtn) {
setTimeout(() => {
submitBtn.click();
}, 100);
}
}

function showSecondPage() {
const allPopups = document.querySelectorAll('[data-ac-popup-container]');
let myPopup = null;

allPopups.forEach(item => {
if (item.dataset.wheelInitialized === 'true') {
try {
myPopup = item.shadowRoot;
} catch (e) {}
}
});

if (!myPopup) return;

const firstPage = myPopup.getElementById('firstPage');
const secondPage = myPopup.getElementById('secondPage');

if (firstPage) firstPage.style.display = 'none';
if (secondPage) 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) % options.length;

handlePrize(options[index]);

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);
}
}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initWheel);
} else {
initWheel();
}

window.addEventListener('load', initWheel);

window.reinitWheel = function() {
const allPopups = document.querySelectorAll('[data-ac-popup-container]');
allPopups.forEach(item => {
if (item.dataset.wheelInitialized === 'true') {
delete item.dataset.wheelInitialized;
}
});
initWheel();
};

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

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

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

Первым делом задайте ширину окна (Форма > width). Размеры холста с колесом фиксированы (420px). Поэтому ширина всего попапа должна быть не менее 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".

примечание

Изменения, внесённые в JS-код, влияют только на поведение виджета для конечного пользователя. В окне предпросмотра редактора актуальный вид колеса отображаться не будет, так как превью формируется на основе статического HTML/CSS шаблона.

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

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

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

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

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

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

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

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

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

Доступно два основных способа размещения попапа на сайте:

  1. Через Менеджер тегов (рекомендуется). В разделе "Появление" привяжите попап к контейнеру Менеджера тегов. Затем настройте триггер — условие, при котором попап будет показан (например, "Таймер" для показа через 10 секунд или "Глубина прокрутки" для показа при скролле 50% страницы). Подробнее о настройке триггеров читайте в отдельной статье.
  2. Вручную. Нажмите кнопку "Опубликовать", скопируйте сгенерированный код и разместите его на нужных страницах вашего сайта.

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

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