Управление подписками

Механизм подписок в ScrollMagic строится вокруг событий, генерируемых объектами Controller и Scene. Каждая сцена может испускать события, связанные с прокруткой, входом в область видимости, выходом из неё, прогрессом анимации и обновлением состояния. Подписки позволяют отслеживать эти изменения и реагировать на них через обработчики.

Ключевые объекты:

  • ScrollMagic.Controller — управляет глобальным состоянием прокрутки
  • ScrollMagic.Scene — описывает отдельный участок прокрутки
  • События (events) — точки входа для подписок

Основные события Scene

Сцена поддерживает ряд встроенных событий, на которые можно подписываться:

  • enter — сцена входит в область действия
  • leave — сцена покидает область действия
  • start — достигнута начальная точка сцены
  • end — достигнута конечная точка сцены
  • progress — изменяется прогресс (0–1)
  • update — происходит обновление состояния
  • change — изменяются параметры сцены
  • shift — смещение позиции сцены

Пример подписки:

scene.on("enter", function (event) {
  console.log("Сцена активирована");
});

Синтаксис подписок

Метод .on() используется для регистрации обработчиков:

scene.on(eventName, callback);

Поддерживается несколько событий одновременно:

scene.on("enter leave", function (event) {
  console.log(event.type);
});

Удаление подписки:

scene.off("enter", callback);

Удаление всех подписок:

scene.off("*");

Объект события

Каждый обработчик получает объект события с полезными свойствами:

  • type — тип события
  • target — ссылка на сцену
  • progress — текущий прогресс (для события progress)
  • state — текущее состояние (BEFORE, DURING, AFTER)
  • scrollDirection — направление прокрутки (FORWARD, REVERSE)

Пример:

scene.on("progress", function (event) {
  console.log(event.progress);
});

Управление жизненным циклом подписок

Подписки должны управляться явно, особенно в динамических приложениях.

Создание и удаление сцен

При удалении сцены важно также удалить подписки:

scene.destroy(true);

Параметр true указывает на удаление всех обработчиков.

Повторное использование обработчиков

Хранение ссылок на функции позволяет корректно удалять подписки:

function onEnter(event) {
  console.log("Вход");
}

scene.on("enter", onEnter);
scene.off("enter", onEnter);

Подписки на Controller

Контроллер также генерирует события, например:

  • update
  • resize

Пример:

controller.on("update", function () {
  console.log("Обновление контроллера");
});

Контекст выполнения обработчиков

По умолчанию this внутри обработчика указывает на объект сцены:

scene.on("enter", function () {
  console.log(this === scene); // true
});

Использование стрелочных функций меняет поведение:

scene.on("enter", () => {
  console.log(this); // внешний контекст
});

Оптимизация подписок

Частые события, такие как progress и update, могут вызываться десятки раз в секунду.

Троттлинг

let lastCall = 0;

scene.on("progress", function (event) {
  const now = Date.now();
  if (now - lastCall > 100) {
    console.log(event.progress);
    lastCall = now;
  }
});

Дебаунс

let timeout;

scene.on("update", function () {
  clearTimeout(timeout);
  timeout = setTimeout(() => {
    console.log("Обновление завершено");
  }, 200);
});

Группировка логики подписок

При большом количестве сцен рекомендуется выносить обработчики в отдельные модули:

function attachSceneHandlers(scene) {
  scene.on("enter", onEnter);
  scene.on("leave", onLeave);
}

Это упрощает поддержку и повторное использование.


Условные подписки

Иногда требуется активировать обработчики только при определённых условиях:

scene.on("progress", function (event) {
  if (event.progress > 0.5) {
    console.log("Больше половины");
  }
});

Комбинирование с анимациями

Подписки часто используются совместно с анимационными библиотеками (например, GSAP):

scene.on("enter", function () {
  gsap.to(".box", { x: 100 });
});

Отладка подписок

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

scene.on("*", function (event) {
  console.log(event.type);
});

Также полезен режим отладки:

scene.addIndicators();

Частые ошибки

1. Утечки памяти

Отсутствие удаления подписок при удалении сцены.

2. Дублирование обработчиков

Повторная регистрация одной и той же функции:

scene.on("enter", handler);
scene.on("enter", handler); // вызов дважды

3. Неправильный контекст

Использование стрелочных функций, где нужен this.


Паттерны управления подписками

Централизованный регистр

const subscriptions = [];

function add(scene, event, handler) {
  scene.on(event, handler);
  subscriptions.push({ scene, event, handler });
}

function clearAll() {
  subscriptions.forEach(({ scene, event, handler }) => {
    scene.off(event, handler);
  });
}

Фабрика сцен с подписками

function createScene(options) {
  const scene = new ScrollMagic.Scene(options);

  scene.on("enter", () => console.log("enter"));
  scene.on("leave", () => console.log("leave"));

  return scene;
}

Декларативный подход

const config = [
  { event: "enter", handler: onEnter },
  { event: "leave", handler: onLeave }
];

config.forEach(({ event, handler }) => {
  scene.on(event, handler);
});

Управление подписками в SPA

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

function cleanup() {
  scene.destroy(true);
  controller.destroy(true);
}

Инициализация при монтировании:

function init() {
  controller = new ScrollMagic.Controller();
  scene = new ScrollMagic.Scene({ duration: 300 });
}

Асинхронные обработчики

Подписки могут использовать асинхронный код:

scene.on("enter", async function () {
  const data = await fetch("/api/data");
  console.log(await data.json());
});

Расширение системы событий

Можно создавать собственные события:

scene.trigger("customEvent", { value: 42 });

scene.on("customEvent", function (event) {
  console.log(event.value);
});

Контроль частоты обновлений через Controller

const controller = new ScrollMagic.Controller({
  refreshInterval: 100
});

Это уменьшает количество событий update.


Связь подписок с DOM

Часто обработчики работают с DOM:

scene.on("enter", function () {
  document.querySelector(".box").classList.add("active");
});

Важно избегать лишних операций:

  • кэшировать селекторы
  • минимизировать перерисовки

Инкапсуляция подписок

Создание классов для управления сценами:

class SceneManager {
  constructor(scene) {
    this.scene = scene;
    this.init();
  }

  init() {
    this.scene.on("enter", this.onEnter.bind(this));
  }

  onEnter() {
    console.log("enter");
  }

  destroy() {
    this.scene.off("enter", this.onEnter);
  }
}

Управление зависимостями

Подписки могут зависеть друг от друга:

scene1.on("enter", function () {
  scene2.enabled(true);
});

Вывод ключевых принципов

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