Многошаговые формы

Многошаговая форма (multi-step form) разбивает ввод данных на логические этапы, улучшая восприятие и снижая когнитивную нагрузку. В контексте ScrollMagic каждый шаг может быть связан с прокруткой страницы, создавая интерактивный сценарий, где переходы между шагами управляются скроллом.

Основные компоненты:

  • Контроллер (Controller) — управляет сценами
  • Сцена (Scene) — описывает триггер, длительность и поведение
  • DOM-структура шагов — отдельные блоки формы
  • Логика состояния — отслеживает текущий шаг и введённые данные

Разметка формы

Каждый шаг формы оформляется как отдельный контейнер:

<form id="multi-step-form">
  <section class="step" data-step="1">
    <h2>Шаг 1</h2>
    <input type="text" name="name" placeholder="Имя">
  </section>

  <section class="step" data-step="2">
    <h2>Шаг 2</h2>
    <input type="email" name="email" placeholder="Email">
  </section>

  <section class="step" data-step="3">
    <h2>Шаг 3</h2>
    <input type="password" name="password" placeholder="Пароль">
  </section>
</form>

CSS подготавливает базовую визуализацию:

.step {
  height: 100vh;
  opacity: 0.3;
  transition: opacity 0.5s ease;
}

.step.active {
  opacity: 1;
}

Инициализация ScrollMagic

const controller = new ScrollMagic.Controller();

Контроллер управляет всеми сценами, связанными с шагами формы.


Создание сцен для шагов

Каждый шаг связывается с отдельной сценой:

document.querySelectorAll('.step').forEach((step, index) => {
  new ScrollMagic.Scene({
    triggerElement: step,
    triggerHook: 0.5,
    duration: '100%'
  })
    .on('enter', () => setActiveStep(index))
    .addTo(controller);
});

Ключевые параметры:

  • triggerElement — элемент, при достижении которого активируется сцена
  • triggerHook — позиция триггера (0 — верх, 1 — низ)
  • duration — длительность эффекта (можно задавать в пикселях или процентах)

Управление активным шагом

function setActiveStep(index) {
  document.querySelectorAll('.step').forEach((step, i) => {
    step.classList.toggle('active', i === index);
  });
}

При входе в сцену шаг становится активным.


Фиксация формы (pinning)

ScrollMagic позволяет «закрепить» форму на экране, пока пользователь прокручивает шаги:

new ScrollMagic.Scene({
  triggerElement: '#multi-step-form',
  triggerHook: 0,
  duration: '300%'
})
  .setPin('#multi-step-form')
  .addTo(controller);

Это создаёт эффект «прокрутки внутри формы», когда сами шаги сменяются, а форма остаётся на месте.


Анимации переходов между шагами

Для плавных переходов используется GSAP:

document.querySelectorAll('.step').forEach((step) => {
  new ScrollMagic.Scene({
    triggerElement: step,
    triggerHook: 0.7,
    reverse: true
  })
    .setTween(step, { y: 50, opacity: 1, duration: 0.5 })
    .addTo(controller);
});

Анимация:

  • сдвиг по оси Y
  • плавное появление

Валидация между шагами

Переход к следующему шагу можно ограничить:

function validateStep(stepElement) {
  const inputs = stepElement.querySelectorAll('input');
  return [...inputs].every(input => input.value.trim() !== '');
}

Использование в сцене:

.on('enter', (event) => {
  if (!validateStep(step)) {
    controller.scrollTo(step);
  }
});

Программная прокрутка

ScrollMagic поддерживает управление скроллом:

controller.scrollTo((newPos) => {
  window.scrollTo({
    top: newPos,
    beh * avior: 'smooth'
  });
});

Переход к следующему шагу:

function goToStep(index) {
  const step = document.querySelectorAll('.step')[index];
  controller.scrollTo(step);
}

Индикатор прогресса

Прогресс формы отображается через прогресс-бар:

<div class="progress">
  <div class="progress-bar"></div>
</div>
.progress {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 5px;
}

.progress-bar {
  height: 100%;
  width: 0;
  background: #3498db;
}

Обновление:

controller.scrollPos(() => window.scrollY);

controller.update(true);

window.addEventListener('scroll', () => {
  const maxScroll = document.body.scrollHeight - window.innerHeight;
  const progress = (window.scrollY / maxScroll) * 100;
  document.querySelector('.progress-bar').style.width = progress + '%';
});

Сохранение состояния формы

Для предотвращения потери данных:

const formData = {};

document.querySelectorAll('input').forEach(input => {
  input.addEventListener('input', (e) => {
    formData[e.target.name] = e.target.value;
  });
});

Восстановление:

window.addEventListener('load', () => {
  Object.keys(formData).forEach(name => {
    const input = document.querySelector(`[name="${name}"]`);
    if (input) input.value = formData[name];
  });
});

Асинхронные шаги

Некоторые шаги могут требовать загрузки данных:

async function loadStepData(step) {
  const response = await fetch('/api/data');
  const data = await response.json();
  step.querySelector('.content').innerText = data.text;
}

Интеграция:

.on('enter', () => {
  if (!step.dataset.loaded) {
    loadStepData(step);
    step.dataset.loaded = true;
  }
});

Обработка завершения формы

Последний шаг может отправлять данные:

function submitForm() {
  fetch('/submit', {
    method: 'POST',
    body: JSON.stringify(formData),
    headers: {
      'Content-Type': 'application/json'
    }
  });
}

Привязка:

if (index === lastStepIndex) {
  submitForm();
}

Оптимизация производительности

  • Минимизировать количество сцен
  • Использовать triggerHook вместо сложных вычислений
  • Отключать reverse, если обратная анимация не требуется
  • Делегировать события вместо навешивания на каждый элемент

Адаптация под мобильные устройства

  • Уменьшить длительность сцен
  • Учитывать инерционный скролл
  • Избегать фиксированных элементов при нестабильном viewport
const isMobile = window.innerWidth < 768;

if (isMobile) {
  controller.enabled(false);
}

Расширение через кастомные события

const event = new CustomEvent('stepChange', {
  detail: { step: index }
});

window.dispatchEvent(event);

Подписка:

window.addEventListener('stepChange', (e) => {
  console.log('Текущий шаг:', e.detail.step);
});

Интеграция с другими библиотеками

ScrollMagic часто используется совместно с:

  • GSAP — для сложных анимаций
  • Barba.js — для переходов между страницами
  • Vue / React — через обёртки или хуки

Пример с GSAP Timeline:

const timeline = gsap.timeline();

timeline
  .from('.step h2', { opacity: 0, y: 20 })
  .from('.step input', { opacity: 0, y: 20 }, '-=0.2');

new ScrollMagic.Scene({
  triggerElement: '.step',
  triggerHook: 0.5
})
  .setTween(timeline)
  .addTo(controller);

Типичные ошибки

  • Слишком большое количество сцен → падение производительности
  • Некорректный duration → неожиданные переходы
  • Отсутствие debounce на scroll-событиях
  • Игнорирование адаптивности

Структурирование кода

Рекомендуется разделять:

  • Логику ScrollMagic
  • Управление состоянием формы
  • Валидацию
  • API-запросы

Пример:

class MultiStepForm {
  constructor() {
    this.controller = new ScrollMagic.Controller();
    this.steps = document.querySelectorAll('.step');
    this.initScenes();
  }

  initScenes() {
    this.steps.forEach((step, index) => {
      new ScrollMagic.Scene({
        triggerElement: step,
        triggerHook: 0.5
      })
        .on('enter', () => this.setActiveStep(index))
        .addTo(this.controller);
    });
  }

  setActiveStep(index) {
    this.steps.forEach((step, i) => {
      step.classList.toggle('active', i === index);
    });
  }
}

new MultiStepForm();

Такой подход упрощает поддержку и масштабирование многошаговой формы.