Resize events

Popper.js — это мощная библиотека для управления позиционированием всплывающих элементов относительно целевых элементов в DOM. Одним из ключевых аспектов, влияющих на корректное позиционирование, является изменение размеров окна или родительских элементов. Без корректной обработки этих изменений попперы могут смещаться, накладываться на другие элементы или частично скрываться.

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

Popper.js использует механизм resize браузера через объект window для отслеживания глобального изменения размеров. Основной способ обеспечить корректное позиционирование при изменении размеров — это интеграция с модулем autoUpdate или ручное добавление слушателей событий:

import { createPopper, autoUpdate } from '@popperjs/core';

const reference = document.querySelector('#button');
const popper = document.querySelector('#tooltip');

const popperInstance = createPopper(reference, popper, {
  placement: 'top',
});

// Автоматическое обновление при изменении размеров окна или скролла
autoUpdate(reference, popper, popperInstance.update);

Ключевой момент: autoUpdate использует комбинацию ResizeObserver, событий scroll и resize для обеспечения динамического позиционирования. Это означает, что при любом изменении размеров целевого элемента, поппера или окна вызовется метод update, корректирующий позицию.

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

Для более тонкой настройки и наблюдения за отдельными элементами используется API ResizeObserver, который позволяет реагировать на изменения размеров конкретного DOM-узла без постоянного контроля окна:

const resizeObserver = new ResizeObserver(() => {
  popperInstance.update();
});

resizeObserver.observe(reference);
resizeObserver.observe(popper);

Особенности:

  • Работает на уровне конкретных элементов, а не глобально.
  • Позволяет избежать лишних вызовов update при изменениях, не влияющих на позиционирование.
  • Совместим с Popper.js начиная с версии 2.0, полностью интегрируется с системой модулей.

Настройка частоты обновлений

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

function debounce(fn, delay) {
  let timer;
  return () => {
    clearTimeout(timer);
    timer = setTimeout(fn, delay);
  };
}

const debouncedUpdate = debounce(() => popperInstance.update(), 100);

window.addEventListener('resize', debouncedUpdate);

Применение дебаунса позволяет уменьшить нагрузку на браузер при постоянном изменении размеров и обеспечивает плавную работу интерфейсов с Popper.js.

Интеграция с динамическим контентом

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

const container = document.querySelector('#container');

const containerObserver = new ResizeObserver(() => {
  popperInstance.update();
});

containerObserver.observe(container);

Popper.js корректно позиционирует элементы даже при асинхронной подгрузке контента, если используется update или autoUpdate в сочетании с ResizeObserver.

Рекомендации по оптимизации

  1. Использовать autoUpdate по возможности — это упрощает работу и снижает количество кода.
  2. Наблюдать только необходимые элементы через ResizeObserver, чтобы избежать лишних вычислений.
  3. Применять дебаунс или троттлинг при сложных интерфейсах с частыми изменениями размеров.
  4. Комбинировать scroll и resize события для полной синхронизации попперов с интерфейсом.
  5. Проверять placement и strategy при значительных изменениях размеров, чтобы избежать смещения за границы контейнера.

Особенности поведения при изменении размеров

  • Если целевой элемент становится меньше или больше, Popper.js автоматически пересчитывает смещение и границы.
  • При изменении размеров окна поппер сохраняет указанное позиционирование (placement), при этом применяя ограничения (preventOverflow, flip) для корректного отображения.
  • ResizeObserver реагирует не только на изменение ширины и высоты, но и на трансформации и масштабирование элементов, что делает его более точным инструментом, чем просто событие resize окна.

Пример комплексной настройки

import { createPopper, autoUpdate } from '@popperjs/core';

const button = document.querySelector('#button');
const tooltip = document.querySelector('#tooltip');
const container = document.querySelector('#container');

const popperInstance = createPopper(button, tooltip, {
  placement: 'bottom',
  modifiers: [
    { name: 'flip', options: { fallbackPlacements: ['top', 'right'] } },
    { name: 'preventOverflow', options: { padding: 8 } },
  ],
});

const observers = [
  new ResizeObserver(() => popperInstance.update()),
  new ResizeObserver(() => popperInstance.update()),
];

observers[0].observe(button);
observers[1].observe(container);

autoUpdate(button, tooltip, popperInstance.update);

В этом примере Popper.js получает полноценный контроль над изменениями размеров как целевого элемента, так и родительского контейнера, обеспечивая стабильное и корректное позиционирование даже в динамически изменяющемся интерфейсе.