Создание собственного адаптера

Waypoints — это библиотека для отслеживания позиции элементов на странице и срабатывания событий при пересечении определённой точки прокрутки. Стандартные адаптеры (jQuery, Zepto, Vanilla) позволяют интегрировать Waypoints с разными фреймворками, но создание собственного адаптера расширяет возможности библиотеки и делает её совместимой с любыми DOM-манипуляторами.


Структура адаптера

Адаптер в Waypoints — это объект, который реализует унифицированный интерфейс для работы с элементами DOM и обеспечивает следующие ключевые методы:

  1. Adapter(element) – конструктор, создающий обёртку над DOM-элементом.
  2. offset() – возвращает текущую позицию элемента относительно документа.
  3. scrollParent() – определяет ближайший прокручиваемый контейнер.
  4. find(selector) – поиск дочерних элементов внутри текущего элемента.
  5. children() – возвращает все дочерние элементы.
  6. parent() – возвращает родительский элемент.
  7. addClass(className) / removeClass(className) – управление CSS-классами.
  8. trigger(event, args) – генерация пользовательских событий.
  9. on(event, handler) / off(event, handler) – привязка и отвязка событий.

Каждый метод адаптера должен корректно работать с нативными DOM-элементами или библиотекой, для которой создаётся адаптер.


Создание конструктора адаптера

Конструктор адаптера получает DOM-элемент и сохраняет его внутри объекта:

function MyAdapter(element) {
    this.element = element;
}

MyAdapter.prototype.offset = function() {
    const rect = this.element.getBoundingClientRect();
    return {
        top: rect.top + window.pageYOffset,
        left: rect.left + window.pageXOffset
    };
};

Пояснение: метод offset() вычисляет позицию элемента относительно документа, используя getBoundingClientRect() и текущую прокрутку страницы.


Определение прокручиваемого контейнера

Метод scrollParent() позволяет Waypoints корректно определять, относительно чего измерять позиции:

MyAdapter.prototype.scrollParent = function() {
    let parent = this.element.parentNode;
    while (parent) {
        const overflowY = window.getComputedStyle(parent).overflowY;
        if (overflowY === 'auto' || overflowY === 'scroll') {
            return parent;
        }
        parent = parent.parentNode;
    }
    return window;
};

Ключевой момент: если ни один родитель не прокручивается, контейнером считается window.


Навигация по DOM

Методы find, children и parent обеспечивают навигацию по DOM:

MyAdapter.prototype.find = function(selector) {
    return Array.from(this.element.querySelectorAll(selector)).map(el => new MyAdapter(el));
};

MyAdapter.prototype.children = function() {
    return Array.from(this.element.children).map(el => new MyAdapter(el));
};

MyAdapter.prototype.parent = function() {
    return new MyAdapter(this.element.parentNode);
};

Особенность: методы должны возвращать новые экземпляры адаптера, чтобы сохранить цепочку вызовов Waypoints.


Управление классами и событиями

Методы для работы с CSS и событиями реализуются следующим образом:

MyAdapter.prototype.addClass = function(className) {
    this.element.classList.add(className);
};

MyAdapter.prototype.removeClass = function(className) {
    this.element.classList.remove(className);
};

MyAdapter.prototype.on = function(event, handler) {
    this.element.addEventListener(event, handler);
};

MyAdapter.prototype.off = function(event, handler) {
    this.element.removeEventListener(event, handler);
};

MyAdapter.prototype.trigger = function(event, args) {
    const customEvent = new CustomEvent(event, { detail: args });
    this.element.dispatchEvent(customEvent);
};

Важный момент: использование CustomEvent обеспечивает передачу дополнительных данных в обработчики событий Waypoints.


Регистрация адаптера в Waypoints

После реализации всех методов адаптер нужно зарегистрировать, чтобы Waypoints мог его использовать:

window.Waypoint.adapters.myAdapter = {
    Adapter: MyAdapter,
    // метод для проверки совместимости, можно возвращать true
    isAdapter: function(el) {
        return el instanceof HTMLElement;
    }
};

Теперь при создании нового Waypoint можно указать adapter: 'myAdapter':

new Waypoint({
    element: document.getElementById('section1'),
    handler: function(direction) {
        console.log('Waypoint сработал:', direction);
    },
    adapter: 'myAdapter',
    offset: '50%'
});

Особенности и рекомендации

  • Все методы адаптера должны возвращать объекты адаптера, а не нативные DOM-элементы, чтобы сохранить единообразие интерфейса.
  • Методы должны корректно работать как с одиночными элементами, так и с коллекциями (массивами).
  • Если библиотека DOM, для которой создаётся адаптер, имеет специфический способ обработки событий или классов, нужно использовать её нативные методы внутри адаптера.
  • При сложных анимациях или динамическом изменении DOM важно, чтобы offset() вычислял актуальное положение элемента при каждом вызове.

Создание собственного адаптера открывает возможность интегрировать Waypoints с любыми фреймворками или нативным DOM, расширяя функциональность и повышая гибкость управления событиями прокрутки и элементами на странице.