Синхронизация нескольких экземпляров

Синхронизация нескольких экземпляров fullPage.js применяется в интерфейсах, где необходимо обеспечить одновременное поведение нескольких независимых областей прокрутки. Типичные сценарии включают:

  • параллельные слайдеры (например, текст + изображения);
  • синхронные панели (контент + навигация);
  • зеркальные анимации в разных контейнерах;
  • сложные лендинги с мультиобластной прокруткой.

Каждый экземпляр fullPage.js управляет своим контейнером, поэтому синхронизация требует явного связывания их состояний.


Базовая инициализация нескольких экземпляров

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

const fp1 = new fullpage('#fullpage1', {
    autoScrolling: true,
    scrollingSpeed: 700
});

const fp2 = new fullpage('#fullpage2', {
    autoScrolling: true,
    scrollingSpeed: 700
});

На этом этапе оба экземпляра работают независимо. Для синхронизации необходимо перехватывать события и инициировать действия во втором экземпляре.


Синхронизация через события

Ключевые события

fullPage.js предоставляет хуки, которые используются для отслеживания состояния:

  • afterLoad
  • onLeave
  • afterSlideLoad
  • onSlideLeave

Наиболее часто используется onLeave, так как он срабатывает до смены секции.


Простейшая синхронизация секций

let isSyncing = false;

const fp1 = new fullpage('#fullpage1', {
    onLeave: function(origin, destination, direction) {
        if (isSyncing) return;

        isSyncing = true;
        fullpage_api.moveTo(destination.index + 1);
        isSyncing = false;
    }
});

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

let isSyncing = false;

const fp1 = new fullpage('#fullpage1', {
    onLeave: function(origin, destination) {
        if (isSyncing) return;

        isSyncing = true;
        fp2_api.moveTo(destination.index + 1);
        isSyncing = false;
    }
});

const fp2 = new fullpage('#fullpage2', {});
const fp2_api = fullpage_api;

Корректное хранение API экземпляров

При инициализации нескольких экземпляров важно сохранить API каждого:

let fp1_api;
let fp2_api;

fp1_api = new fullpage('#fullpage1', {
    afterRender: function() {
        fp1_api = this;
    }
});

fp2_api = new fullpage('#fullpage2', {
    afterRender: function() {
        fp2_api = this;
    }
});

В более современных версиях API возвращается напрямую:

const fp1_api = new fullpage('#fullpage1', {});
const fp2_api = new fullpage('#fullpage2', {});

Предотвращение зацикливания

Основная проблема синхронизации — бесконечный цикл:

  1. fp1 вызывает переход fp2
  2. fp2 вызывает переход fp1
  3. цикл повторяется

Решение — флаг блокировки:

let isSyncing = false;

function sync(from, to, index) {
    if (isSyncing) return;

    isSyncing = true;
    to.moveTo(index);
    isSyncing = false;
}

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

onLeave: function(origin, destination) {
    sync(fp1_api, fp2_api, destination.index + 1);
}

Двусторонняя синхронизация

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

let isSyncing = false;

function sync(targetApi, index) {
    if (isSyncing) return;

    isSyncing = true;
    targetApi.moveTo(index);
    isSyncing = false;
}

const fp1_api = new fullpage('#fullpage1', {
    onLeave: function(origin, destination) {
        sync(fp2_api, destination.index + 1);
    }
});

const fp2_api = new fullpage('#fullpage2', {
    onLeave: function(origin, destination) {
        sync(fp1_api, destination.index + 1);
    }
});

Синхронизация слайдов внутри секций

fullPage.js поддерживает горизонтальные слайды. Для их синхронизации используются события:

  • afterSlideLoad
  • onSlideLeave

Пример:

let isSyncingSlides = false;

const fp1_api = new fullpage('#fullpage1', {
    onSlideLeave: function(section, origin, destination) {
        if (isSyncingSlides) return;

        isSyncingSlides = true;
        fp2_api.moveSlideRight(destination.index);
        isSyncingSlides = false;
    }
});

Альтернативный вариант с точным управлением:

fp2_api.moveTo(section.index + 1, destination.index);

Синхронизация с разной структурой

Иногда количество секций отличается. В этом случае используется сопоставление:

const map = {
    1: 1,
    2: 3,
    3: 5
};

onLeave: function(origin, destination) {
    const targetIndex = map[destination.index + 1];
    if (targetIndex) {
        fp2_api.moveTo(targetIndex);
    }
}

Частичная синхронизация

Не всегда требуется синхронизировать все секции:

onLeave: function(origin, destination) {
    if (destination.index < 3) {
        fp2_api.moveTo(destination.index + 1);
    }
}

Синхронизация с задержкой

Используется для создания эффектов:

onLeave: function(origin, destination) {
    setTimeout(() => {
        fp2_api.moveTo(destination.index + 1);
    }, 300);
}

Управление скоростью прокрутки

Для визуальной согласованности:

const fp1_api = new fullpage('#fullpage1', {
    scrollingSpeed: 700
});

const fp2_api = new fullpage('#fullpage2', {
    scrollingSpeed: 700
});

Несовпадение скорости приводит к визуальному рассинхрону.


Синхронизация через кастомные события

Альтернативный подход — использование событий DOM:

document.addEventListener('fpSync', (e) => {
    fp2_api.moveTo(e.detail.index);
});

onLeave: function(origin, destination) {
    document.dispatchEvent(new CustomEvent('fpSync', {
        detail: { index: destination.index + 1 }
    }));
}

Преимущество — слабая связанность компонентов.


Использование общего контроллера

Централизованная архитектура:

class SyncController {
    constructor() {
        this.lock = false;
    }

    sync(source, targets, index) {
        if (this.lock) return;

        this.lock = true;
        targets.forEach(t => t.moveTo(index));
        this.lock = false;
    }
}

const controller = new SyncController();

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

onLeave: function(origin, destination) {
    controller.sync(fp1_api, [fp2_api], destination.index + 1);
}

Синхронизация более двух экземпляров

const instances = [fp1_api, fp2_api, fp3_api];

function syncAll(index) {
    instances.forEach(api => api.moveTo(index));
}

С защитой от циклов:

let lock = false;

function syncAll(index) {
    if (lock) return;

    lock = true;
    instances.forEach(api => api.moveTo(index));
    lock = false;
}

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

При большом количестве экземпляров:

  • отключение лишних эффектов (css3: false при необходимости);
  • минимизация количества обработчиков;
  • debounce для событий:
let timer;

function debouncedSync(index) {
    clearTimeout(timer);
    timer = setTimeout(() => {
        syncAll(index);
    }, 50);
}

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

1. Зацикливание событий Отсутствие блокировки приводит к бесконечным вызовам.

2. Потеря контекста API Использование fullpage_api вместо конкретного экземпляра.

3. Несовпадение структуры секций Отсутствие маппинга индексов.

4. Разная скорость анимации Приводит к визуальному рассинхрону.


Расширенные сценарии

Синхронизация с внешними компонентами

onLeave: function(origin, destination) {
    slider.setActive(destination.index);
}

Интеграция с анимациями

onLeave: function(origin, destination) {
    animateSection(destination.index);
}

Архитектурные подходы

Связанная синхронизация Прямые вызовы между экземплярами.

Событийная модель Использование глобальных событий.

Контроллер (рекомендуется) Централизованное управление состоянием.


Поведение при отключенном autoScrolling

Если используется scrollBar: true, синхронизация выполняется через отслеживание scroll:

window.addEventListener('scroll', () => {
    const index = Math.round(window.scrollY / window.innerHeight);
    fp2_api.moveTo(index + 1);
});

Гибридная синхронизация

Комбинация вертикальной и горизонтальной:

onLeave: function(origin, destination) {
    fp2_api.moveTo(destination.index + 1, 0);
}

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

Хранение текущей секции:

let currentSection = 1;

onLeave: function(origin, destination) {
    currentSection = destination.index + 1;
}

Используется для восстановления состояния или синхронизации при инициализации.


Синхронизация при динамическом добавлении секций

После изменения DOM:

fp1_api.reBuild();
fp2_api.reBuild();

И повторная синхронизация:

fp2_api.moveTo(fp1_api.getActiveSection().index + 1);