Обертки и утилиты

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

Назначение оберток:

  • уменьшение количества повторяющегося кода
  • централизованное управление контроллером
  • упрощение конфигурации сцен
  • интеграция с другими библиотеками (например, анимационными)

Простейшая обертка для создания сцен

class ScrollScene {
  constructor({ trigger, duration = 0, offset = 0, hook = 0.5 }) {
    this.scene = new ScrollMagic.Scene({
      triggerElement: trigger,
      duration,
      offset,
      triggerHook: hook
    });
  }

  addTo(controller) {
    this.scene.addTo(controller);
    return this;
  }

  setTween(tween) {
    this.scene.setTween(tween);
    return this;
  }

  on(event, callback) {
    this.scene.on(event, callback);
    return this;
  }
}

Преимущества:

  • читаемый декларативный синтаксис
  • цепочки вызовов (fluent interface)
  • скрытие внутренней реализации ScrollMagic

Централизация контроллера

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

class ScrollManager {
  constructor() {
    this.controller = new ScrollMagic.Controller();
    this.scenes = [];
  }

  addScene(scene) {
    scene.addTo(this.controller);
    this.scenes.push(scene);
  }

  destroy() {
    this.scenes.forEach(scene => scene.scene.destroy(true));
    this.controller.destroy(true);
  }
}

const scrollManager = new ScrollManager();

Ключевые моменты:

  • единый контроллер уменьшает нагрузку
  • упрощает управление жизненным циклом
  • облегчает отладку

Фабрики сцен

Фабричный подход позволяет быстро создавать типовые сцены.

function createFadeScene(selector) {
  return new ScrollScene({
    trigger: selector,
    duration: 200
  }).setTween(
    gsap.from(selector, { opacity: 0, y: 50 })
  );
}

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

scrollManager.addScene(createFadeScene('.block'));

Преимущества фабрик:

  • повторное использование логики
  • единый стиль анимаций
  • ускорение разработки

Группировка сцен

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

class SceneGroup {
  constructor() {
    this.scenes = [];
  }

  add(scene) {
    this.scenes.push(scene);
    return this;
  }

  addTo(controller) {
    this.scenes.forEach(scene => scene.addTo(controller));
  }

  destroy() {
    this.scenes.forEach(scene => scene.scene.destroy(true));
  }
}

Применение:

  • управление блоками интерфейса
  • массовое включение/отключение
  • оптимизация при SPA-навигации

Утилиты для работы с DOM

ScrollMagic не предоставляет встроенных инструментов для поиска элементов, поэтому часто используются вспомогательные функции.

function selectAll(selector) {
  return Array.from(document.querySelectorAll(selector));
}

function createScenesForElements(selector, factory) {
  return selectAll(selector).map(el => factory(el));
}

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

const scenes = createScenesForElements('.item', el =>
  createFadeScene(el)
);

scenes.forEach(scene => scrollManager.addScene(scene));

Дебаунс и троттлинг

При обработке событий прокрутки важно ограничивать частоту вызовов.

function debounce(fn, delay) {
  let timeout;
  return function () {
    clearTimeout(timeout);
    timeout = setTimeout(() => fn.apply(this, arguments), delay);
  };
}

Применение:

window.addEventListener('resize', debounce(() => {
  scrollManager.controller.update(true);
}, 200));

Зачем это нужно:

  • уменьшение нагрузки на браузер
  • предотвращение лагов
  • стабильная работа анимаций

Утилиты для адаптивности

ScrollMagic сцены часто требуют пересчета при изменении размеров экрана.

function isMobile() {
  return window.innerWidth <= 768;
}

Пример адаптации:

const scene = new ScrollScene({
  trigger: '.section',
  duration: isMobile() ? 100 : 300
});

Lazy-инициализация сцен

Создание сцен только при необходимости уменьшает нагрузку.

function lazyScene(selector, factory) {
  let initialized = false;

  return () => {
    if (!initialized) {
      initialized = true;
      return factory(selector);
    }
  };
}

Интеграция с GSAP через утилиты

ScrollMagic часто используется вместе с GSAP, поэтому создаются вспомогательные функции:

function animateOnScroll(selector, animation) {
  return new ScrollScene({
    trigger: selector,
    duration: 300
  }).setTween(animation);
}

Пример:

scrollManager.addScene(
  animateOnScroll('.box', gsap.to('.box', { x: 200 }))
);

Кэширование элементов

Повторные запросы к DOM — дорогостоящая операция.

const elementCache = new Map();

function getElement(selector) {
  if (!elementCache.has(selector)) {
    elementCache.set(selector, document.querySelector(selector));
  }
  return elementCache.get(selector);
}

Логирование и отладка

Создание утилиты для логирования событий сцен:

function debugScene(scene, name) {
  scene.on('enter', () => console.log(`${name}: enter`));
  scene.on('leave', () => console.log(`${name}: leave`));
}

Плагины и расширения

ScrollMagic поддерживает расширения, которые можно оборачивать в удобные интерфейсы.

Пример — индикаторы:

function addIndicators(scene) {
  scene.scene.addIndicators({
    colorStart: 'green',
    colorEnd: 'red'
  });
}

Комбинирование утилит

Сильная сторона архитектуры — возможность комбинировать обертки:

const scene = createFadeScene('.card');

debugScene(scene, 'Card Animation');

scrollManager.addScene(scene);

Архитектурные рекомендации

Разделение ответственности:

  • ScrollManager — управление контроллером
  • SceneFactory — создание сцен
  • Utils — вспомогательные функции

Повторное использование:

  • избегание жестко заданных селекторов
  • параметризация функций

Модульность:

  • разбиение по файлам
  • независимость компонентов

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

  • создание нескольких контроллеров без необходимости
  • отсутствие очистки сцен при удалении компонентов
  • избыточные вычисления при скролле
  • прямое манипулирование сценами без абстракции

Расширенные подходы

Декларативная конфигурация

const config = [
  { selector: '.a', animation: { opacity: 0 } },
  { selector: '.b', animation: { x: 100 } }
];

config.forEach(item => {
  scrollManager.addScene(
    animateOnScroll(
      item.selector,
      gsap.from(item.selector, item.animation)
    )
  );
});

Использование data-атрибутов

<div class="anim" data-duration="200"></div>
selectAll('.anim').forEach(el => {
  const duration = el.dataset.duration;

  scrollManager.addScene(
    new ScrollScene({
      trigger: el,
      duration
    })
  );
});

Производительность

  • минимизация количества сцен
  • объединение анимаций
  • отключение сцен вне области видимости
  • использование requestAnimationFrame при необходимости

Поддержка SPA

При использовании фреймворков важно корректно уничтожать сцены:

function cleanup() {
  scrollManager.destroy();
}

Резюме практики

Обертки и утилиты превращают ScrollMagic из низкоуровневого инструмента в гибкую архитектурную систему. Правильная организация кода позволяет:

  • масштабировать проект
  • легко изменять поведение сцен
  • интегрировать сторонние библиотеки
  • поддерживать читаемость и стабильность