Visual regression testing

Popper.js — это библиотека для управления позиционированием элементов на веб-странице. Основная цель — обеспечивать динамическое позиционирование всплывающих элементов, таких как тултипы, выпадающие меню, поповеры и модальные окна, относительно целевого элемента (reference element). Основные понятия:

  • Popper — элемент, который позиционируется.
  • Reference — элемент, относительно которого позиционируется Popper.
  • Modifiers — функции, которые изменяют поведение Popper, например, смещение, предотвращение выхода за границы окна или изменение стратегии позиционирования.
  • Placement — направление относительно reference: top, bottom, left, right и их вариации с -start и -end.

Создание Popper

Popper создается через функцию createPopper(reference, popper, options):

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

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

createPopper(reference, popper, {
  placement: 'bottom',
  modifiers: [
    {
      name: 'offset',
      options: {
        offset: [0, 8], // смещение по оси X и Y
      },
    },
  ],
});
  • placement определяет начальное направление Popper относительно reference.
  • Массив modifiers позволяет детально управлять поведением: смещение, адаптация к границам, скрытие при отсутствии видимости reference.

Модификаторы (Modifiers)

Модификаторы — это ключевой инструмент Popper.js. Они применяются последовательно и могут менять положение, стиль и стратегию Popper. Основные встроенные модификаторы:

  1. offset — смещает Popper относительно reference.
  2. preventOverflow — предотвращает выход Popper за пределы контейнера.
  3. flip — автоматически изменяет placement, если Popper не помещается.
  4. arrow — позиционирует стрелку Popper относительно reference.
  5. computeStyles — управляет стилями position, transform и top/left Popper.

Пример использования нескольких модификаторов:

createPopper(reference, popper, {
  placement: 'top',
  modifiers: [
    { name: 'offset', options: { offset: [0, 10] } },
    { name: 'flip', options: { fallbackPlacements: ['right', 'left'] } },
    { name: 'preventOverflow', options: { padding: 5 } },
  ],
});

Стратегии позиционирования

Popper.js поддерживает две стратегии позиционирования:

  • absolute — позиционирование относительно ближайшего позиционированного предка.
  • fixed — позиционирование относительно окна, игнорируя скроллинг.

Установка стратегии через модификатор computeStyles:

modifiers: [
  {
    name: 'computeStyles',
    options: {
      strategy: 'fixed',
    },
  },
]

Работа с стрелками (Arrow)

Чтобы стрелка Popper корректно позиционировалась, необходимо указать элемент стрелки и включить модификатор arrow:

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

createPopper(reference, popper, {
  placement: 'bottom',
  modifiers: [
    {
      name: 'arrow',
      options: { element: arrow, padding: 5 },
    },
  ],
});
  • element — DOM-элемент стрелки.
  • padding — расстояние от границ Popper.

Динамическое обновление Popper

Popper.js позволяет обновлять положение Popper в реальном времени, когда меняется размер или положение reference:

const instance = createPopper(reference, popper);

instance.update(); // вручную пересчитывает позицию

Для постоянного обновления можно использовать наблюдатель ResizeObserver или события скролла/изменения размера окна.

Destroy и очистка ресурсов

После использования Popper важно корректно уничтожать экземпляр, чтобы не было утечек памяти:

const instance = createPopper(reference, popper);

// по завершении работы
instance.destroy();

Расширенные возможности через кастомные модификаторы

Custom modifiers позволяют создавать уникальное поведение Popper:

const customModifier = {
  name: 'logPosition',
  enabled: true,
  phase: 'afterWrite',
  fn({ state }) {
    console.log(`Popper position: ${state.styles.popper.top}, ${state.styles.popper.left}`);
  },
};

createPopper(reference, popper, {
  modifiers: [customModifier],
});
  • phase — стадия жизненного цикла Popper (read, main, write).
  • fn — функция, которая получает состояние и может изменять позиционирование.

Интеграция с фреймворками

Popper.js широко используется с React, Vue и Angular. В React существуют обертки вроде @popperjs/react, которые упрощают создание компонента Popper через хук:

import { usePopper } from 'react-popper';

const [referenceElement, setReferenceElement] = useState(null);
const [popperElement, setPopperElement] = useState(null);

const { styles, attributes } = usePopper(referenceElement, popperElement, {
  placement: 'right',
});

return (
  <>
    <button ref={setReferenceElement}>Click me</button>
    <div ref={setPopperElement} style={styles.popper} {...attributes.popper}>
      Tooltip content
    </div>
  </>
);

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

  • Использовать минимальное количество обновлений через update() и наблюдатели.
  • Комбинировать flip и preventOverflow для адаптивного позиционирования.
  • Кастомные модификаторы должны работать только при необходимости, чтобы не увеличивать время рендеринга.
  • Для больших приложений рекомендуется создавать один экземпляр Popper на элемент и динамически менять его контент.

Особенности работы с масштабированными и прокручиваемыми контейнерами

Popper корректно вычисляет позиции даже при масштабировании или прокрутке, если включены:

  • boundary — контейнер, относительно которого проверяется overflow.
  • tether — при включении удерживает Popper в пределах boundary даже при flip.
modifiers: [
  {
    name: 'preventOverflow',
    options: { boundary: document.body, tether: true },
  },
]
  • Это позволяет создавать сложные интерфейсы с динамическим размещением тултипов и меню без ручного контроля координат.

Использование Popper в визуальном тестировании

Для визуальных регрессионных тестов важно, чтобы Popper корректно рендерился в статическом состоянии:

  • Устанавливать фиксированные размеры reference и Popper.
  • Отключать динамическое flip и tether, чтобы позиции были предсказуемыми.
  • Использовать snapshot-тесты с библиотеками вроде Cypress или Jest для проверки положения всплывающих элементов.

Это обеспечивает стабильность интерфейса и предотвращает ложные срабатывания тестов при изменении контента или размера окна.