Создание собственных плагинов

Библиотека ScrollMagic изначально спроектирована с учётом расширяемости. Её ядро предоставляет базовые сущности — Controller, Scene и механизм событий — а дополнительные возможности реализуются через плагины. Такой подход позволяет добавлять функциональность без изменения исходного кода библиотеки.

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


Основные принципы создания плагинов

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

  2. Расширение прототипов Чаще всего плагины добавляют методы в ScrollMagic.Scene.prototype или ScrollMagic.Controller.prototype.

  3. Использование событий Плагины взаимодействуют с системой через события (enter, leave, progress, update).

  4. Идемпотентность Повторное подключение плагина не должно ломать поведение.


Структура базового плагина

Минимальный шаблон плагина:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['scrollmagic'], factory);
    } else if (typeof exports === 'object') {
        module.exports = factory(require('scrollmagic'));
    } else {
        factory(root.ScrollMagic);
    }
}(this, function (ScrollMagic) {

    if (!ScrollMagic) {
        throw new Error('ScrollMagic is required');
    }

    ScrollMagic.Scene.prototype.myPluginMethod = function () {
        // логика плагина
        return this;
    };

}));

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

  • Используется UMD-паттерн для совместимости
  • Расширяется прототип Scene
  • Возвращается this для цепочки вызовов

Добавление методов в Scene

Простейший способ расширения:

ScrollMagic.Scene.prototype.setBackgroundColor = function (color) {
    this.on("enter", function () {
        document.body.style.backgroundColor = color;
    });

    return this;
};

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

new ScrollMagic.Scene({ triggerElement: "#trigger" })
    .setBackgroundColor("black")
    .addTo(controller);

Работа с внутренними событиями

ScrollMagic активно использует событийную модель. Плагин может подписываться на события:

  • start
  • end
  • enter
  • leave
  • progress
  • update

Пример:

ScrollMagic.Scene.prototype.logProgress = function () {
    this.on("progress", function (event) {
        console.log("Progress:", event.progress);
    });

    return this;
};

Перехват и расширение существующих методов

Иногда требуется изменить поведение уже существующих методов. Для этого используется обёртка:

const originalAddTo = ScrollMagic.Scene.prototype.addTo;

ScrollMagic.Scene.prototype.addTo = function (controller) {
    console.log("Scene добавляется в controller");

    return originalAddTo.call(this, controller);
};

Важно: Всегда сохранять оригинальную функцию и вызывать её через call.


Создание плагина с настройками

Плагин может принимать параметры:

ScrollMagic.Scene.prototype.highlight = function (options) {
    const settings = Object.assign({
        color: "yellow",
        duration: 0.3
    }, options);

    this.on("enter", function () {
        this.triggerElement().style.transition = `background ${settings.duration}s`;
        this.triggerElement().style.background = settings.color;
    });

    this.on("leave", function () {
        this.triggerElement().style.background = "";
    });

    return this;
};

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

scene.highlight({ color: "red", duration: 0.5 });

Расширение Controller

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

ScrollMagic.Controller.prototype.getScenesCount = function () {
    return this.info("size");
};

Создание полноценного плагина с состоянием

Для сложной логики требуется хранение состояния:

ScrollMagic.Scene.prototype.counter = function () {
    let count = 0;

    this.on("enter", function () {
        count++;
        console.log("Входов:", count);
    });

    return this;
};

Использование приватных данных

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

ScrollMagic.Scene.prototype.timer = function () {
    let startTime = null;

    this.on("enter", function () {
        startTime = Date.now();
    });

    this.on("leave", function () {
        const duration = Date.now() - startTime;
        console.log("Время в зоне:", duration);
    });

    return this;
};

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

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

ScrollMagic.Scene.prototype.animateCSS = function (className) {
    this.on("enter", function () {
        this.triggerElement().classList.add(className);
    });

    this.on("leave", function () {
        this.triggerElement().classList.remove(className);
    });

    return this;
};

Работа с жизненным циклом Scene

Жизненный цикл сцены:

  1. Создание
  2. Добавление в контроллер
  3. Обновление (scroll/resize)
  4. Уничтожение

Плагин может реагировать на уничтожение:

ScrollMagic.Scene.prototype.cleanup = function () {
    this.on("destroy", function () {
        console.log("Scene уничтожена");
    });

    return this;
};

Создание цепочек вызовов

Для удобства API все методы должны возвращать this:

scene
    .highlight()
    .logProgress()
    .timer();

Обработка ошибок

Плагин должен проверять входные данные:

ScrollMagic.Scene.prototype.safeColor = function (color) {
    if (typeof color !== "string") {
        throw new Error("Color должен быть строкой");
    }

    return this;
};

Избежание конфликтов

Чтобы избежать конфликтов имён:

  • Использовать уникальные имена методов
  • Префиксы (sm_, plugin_)
  • Проверять существование метода
if (!ScrollMagic.Scene.prototype.myPlugin) {
    ScrollMagic.Scene.prototype.myPlugin = function () {};
}

Модульная организация

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

plugin/
├── index.js
├── core.js
├── events.js
└── utils.js

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

При разработке плагинов важно учитывать:

  • минимизацию подписок на события
  • отсутствие тяжёлых операций в progress
  • кэширование DOM-элементов

Плохой пример:

this.on("progress", function () {
    document.querySelectorAll(".item");
});

Хороший пример:

const items = document.querySelectorAll(".item");

this.on("progress", function () {
    // работа с уже найденными элементами
});

Тестирование плагинов

Проверяются:

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

Документирование API плагина

Каждый метод должен иметь описание:

/**
 * Подсветка элемента при входе в viewport
 * @param {Object} options
 * @param {string} options.color
 * @param {number} options.duration
 */

Расширенные техники

1. Комбинирование плагинов

scene
    .highlight({ color: "blue" })
    .timer()
    .logProgress();

2. Декораторы

Плагин может изменять поведение других методов.

3. Lazy-инициализация

let initialized = false;

this.on("enter", function () {
    if (!initialized) {
        initialized = true;
        // инициализация
    }
});

Практический пример: плагин фиксации элемента

ScrollMagic.Scene.prototype.pinCustom = function () {
    let element = this.triggerElement();

    this.on("enter", function () {
        element.style.position = "fixed";
        element.style.top = "0";
    });

    this.on("leave", function () {
        element.style.position = "";
        element.style.top = "";
    });

    return this;
};

Подключение плагина

<script src="ScrollMagic.min.js"></script>
<script src="plugin.js"></script>

или через сборщик:

import ScrollMagic from "scrollmagic";
import "./plugin";

Распространение плагинов

Плагин оформляется как npm-пакет:

  • package.json
  • описание API
  • примеры использования
  • версия ScrollMagic

Поддержка совместимости

Важно учитывать:

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

Расширение через композицию

Вместо изменения ядра — создание надстроек:

function createAdvancedScene(options) {
    return new ScrollMagic.Scene(options)
        .highlight()
        .timer();
}

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

Если плагин требует внешнюю библиотеку:

if (!window.SomeLibrary) {
    throw new Error("SomeLibrary required");
}

Подход к масштабированию

При росте проекта:

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

Контроль утечек памяти

При уничтожении сцены необходимо очищать:

  • таймеры
  • обработчики событий
  • ссылки на DOM
this.on("destroy", function () {
    clearInterval(timer);
});