Создание виртуального reference

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

Виртуальный reference представляет собой объект с методом getBoundingClientRect и опциональным свойством contextElement:

const virtualReference = {
  getBoundingClientRect: () => ({
    width: 100,
    height: 50,
    top: 200,
    bottom: 250,
    left: 300,
    right: 400,
  }),
  contextElement: document.body // необязательный, нужен для scroll context
};

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

  • getBoundingClientRect должен возвращать объект с полями top, bottom, left, right, width, height.
  • contextElement определяет контекст для scroll и overflow. Если не указан, Popper использует document.body по умолчанию.
  • Виртуальный reference позволяет позиционировать popper относительно произвольной координаты, не создавая вспомогательных DOM-элементов.

Создание Popper с виртуальным reference

Popper создается через функцию createPopper(reference, popper, options). Вместо DOM-элемента в качестве reference можно передать виртуальный объект.

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

const popperElement = document.querySelector('#tooltip');

const virtualReference = {
  getBoundingClientRect: () => ({
    width: 0,
    height: 0,
    top: 150,
    bottom: 150,
    left: 400,
    right: 400
  })
};

const popperInstance = createPopper(virtualReference, popperElement, {
  placement: 'top',
  modifiers: [
    {
      name: 'offset',
      options: {
        offset: [0, 8]
      }
    }
  ]
});

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

  • В данном примере popper будет отображаться на 8 пикселей выше виртуальной точки (400, 150).
  • Можно динамически менять координаты getBoundingClientRect, чтобы перемещать popper без изменения DOM.

Динамическое обновление позиции

Popper.js предоставляет метод update(), который пересчитывает положение popper относительно reference. Для виртуального reference это критично, так как координаты могут изменяться в реальном времени.

let x = 400;
let y = 150;

const virtualReference = {
  getBoundingClientRect: () => ({
    width: 0,
    height: 0,
    top: y,
    bottom: y,
    left: x,
    right: x
  })
};

setInterval(() => {
  x += 1;
  y += 1;
  popperInstance.update();
}, 16);

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


Использование виртуальных reference для курсора

Частая практика — привязка всплывающих подсказок к положению курсора. Для этого создается виртуальный reference, обновляемый при движении мыши:

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

const virtualReference = {
  getBoundingClientRect: () => ({
    width: 0,
    height: 0,
    top: mouseY,
    bottom: mouseY,
    left: mouseX,
    right: mouseX
  })
};

const popperInstance = createPopper(virtualReference, tooltip, {
  placement: 'right',
});

let mouseX = 0;
let mouseY = 0;

document.addEventListener('mousemove', (event) => {
  mouseX = event.clientX;
  mouseY = event.clientY;
  popperInstance.update();
});

Особенности такого подхода:

  • Позволяет отображать tooltip в любой точке экрана без привязки к DOM-элементу.
  • Высокая производительность, так как нет необходимости перемещать реальные элементы reference.
  • Совместимо с любыми модификаторами Popper.js, включая flip, preventOverflow и offset.

Виртуальные reference и scroll/overflow контекст

Popper учитывает scroll и overflow контейнеры reference через contextElement. Для виртуальных reference без явного контекста scroll поведение может отличаться от обычного DOM-элемента.

const virtualReference = {
  getBoundingClientRect: () => ({
    width: 50,
    height: 50,
    top: 100,
    bottom: 150,
    left: 200,
    right: 250
  }),
  contextElement: document.querySelector('.scroll-container')
};
  • contextElement задает точку отсчета для всех вычислений.
  • При прокрутке scroll контейнера popper корректно сохраняет позицию относительно виртуального reference.

Применение в сложных интерфейсах

Виртуальные reference полезны в следующих сценариях:

  1. SVG-элементы и канвас — нет реальных HTML-узлов, но требуется всплывающее позиционирование.
  2. Анимации и динамические координаты — popper следует за движущимися объектами.
  3. Абстрактные компоненты — tooltip, dropdown или меню, не привязанные к конкретному DOM-узлу.
  4. Привязка к координатам мыши — подсказки, метки и интерактивные эффекты.

Использование виртуального reference превращает Popper.js из инструмента для простого DOM-позиционирования в мощный движок для динамических интерфейсов с полной свободой в определении reference.