Lifecycle hooks

Popper.js предоставляет мощный механизм управления позиционированием элементов через lifecycle hooks — функции обратного вызова, которые позволяют вмешиваться в процесс расчёта и обновления положения всплывающих элементов (popper). Эти хуки обеспечивают полный контроль над поведением поппера на всех этапах его жизненного цикла.


Основные этапы жизненного цикла Popper.js

Жизненный цикл Popper.js можно разделить на несколько ключевых этапов:

  1. Создание экземпляра При вызове createPopper(reference, popper, options) происходит инициализация объекта Popper. На этом этапе хуки могут использоваться для настройки начальных состояний, установки стилей и подготовки данных о позиционировании.

  2. Перед расчётом позиции (beforeWrite / beforeUpdate) Хуки этого типа вызываются до вычисления координат поппера. Здесь можно изменить размеры, настроить ограничения или модифицировать reference/ popper элементы.

    const popperInstance = Popper.createPopper(referenceElement, popperElement, {
      modifiers: [
        {
          name: 'beforeUpdateHook',
          phase: 'beforeWrite',
          fn({ state }) {
            console.log('Состояние перед вычислением:', state);
          }
        }
      ]
    });

    Ключевые свойства объекта state на этом этапе:

    • state.rects.reference — размеры и положение reference элемента.
    • state.rects.popper — размеры и положение поппера.
    • state.modifiersData — данные, полученные от других модификаторов.
    • state.options — текущие опции Popper.js.
  3. Расчёт позиции (main / compute) На этом этапе Popper.js вычисляет точные координаты и оптимальное расположение всплывающего элемента. Модификаторы с фазой main выполняются после расчёта, но до применения стилей.

    Пример модификатора для изменения смещения:

    const offsetModifier = {
      name: 'customOffset',
      phase: 'main',
      fn({ state }) {
        state.modifiersData.popperOffsets.x += 10;
        state.modifiersData.popperOffsets.y += 5;
      }
    };
  4. Применение изменений (write) Фаза write выполняется после расчёта позиции и отвечает за непосредственное применение стилей и атрибутов к DOM.

    const writeHook = {
      name: 'logPosition',
      phase: 'write',
      fn({ state }) {
        console.log('Поппер установлен на:', state.styles.popper);
      }
    };

    На этом этапе можно вмешиваться в:

    • state.styles.popper — стили, которые будут применены к popper элементу.
    • state.attributes.popper — атрибуты DOM, например aria или data-*.
  5. Обновление и уничтожение После применения стилей Popper.js продолжает отслеживать изменения, которые могут повлиять на позицию (resize, scroll, mutation). Хуки могут быть вызваны при каждом обновлении:

    • afterWrite — вызывается после применения стилей, полезен для логирования или запуска анимаций.
    • destroy — вызывается при вызове popperInstance.destroy(), позволяет очищать ресурсы и слушатели событий.

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

  • Фазы хуков Popper.js использует следующие основные фазы: beforeRead → read → afterRead → beforeMain → main → afterMain → beforeWrite → write → afterWrite. Каждая фаза имеет своё назначение и возможность влиять на объект state.

  • Модификаторы как хуки Любой пользовательский модификатор может выступать в роли lifecycle hook, если указать фазу и функцию fn. Это позволяет:

    • Подгонять смещения в реальном времени.
    • Изменять размеры или поведение поппера.
    • Внедрять кастомные анимации или логирование.
  • Контекст state Все хуки получают один и тот же объект state, содержащий:

    • elements — ссылки на popper и reference.
    • modifiersData — промежуточные результаты модификаторов.
    • styles и attributes — применяемые к DOM.
  • Асинхронные операции Для асинхронных действий стоит использовать отдельный механизм, так как хуки выполняются синхронно в жизненном цикле Popper.js. Например, для динамической загрузки данных лучше обновлять поппер через popperInstance.update() после завершения асинхронной операции.


Примеры практического использования

  1. Добавление динамического смещения

    const dynamicOffset = {
      name: 'dynamicOffset',
      phase: 'main',
      fn({ state }) {
        const scrollFactor = window.scrollY / 10;
        state.modifiersData.popperOffsets.y += scrollFactor;
      }
    };
  2. Логирование координат перед применением

    const logBeforeWrite = {
      name: 'logBeforeWrite',
      phase: 'beforeWrite',
      fn({ state }) {
        console.log('Координаты до применения:', state.modifiersData.popperOffsets);
      }
    };
  3. Очистка слушателей при уничтожении

    const cleanupHook = {
      name: 'cleanup',
      phase: 'destroy',
      fn({ state }) {
        console.log('Popper уничтожен, очищаем ресурсы');
      }
    };

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

  • Lifecycle hooks позволяют полностью контролировать процесс позиционирования.
  • Фазы beforeWrite и write оптимальны для модификации стилей и атрибутов.
  • Модификаторы могут быть использованы как хуки, с доступом к объекту state.
  • Для асинхронной логики необходимо инициировать обновление через update().
  • Правильное использование хуков повышает гибкость и адаптивность интерфейсов, особенно для сложных всплывающих элементов.

Lifecycle hooks Popper.js представляют собой универсальный инструмент для управления позиционированием, предоставляя детальный доступ к состоянию и возможность вмешательства на всех этапах жизненного цикла.