IFrames

Popper.js предоставляет гибкие механизмы позиционирования элементов относительно других элементов в DOM. Когда целевым элементом является iFrame, возникают специфические нюансы, связанные с изолированным контекстом окна (window) и документом (document). Эти нюансы необходимо учитывать при создании всплывающих подсказок, тултипов или других плавающих элементов поверх iFrame.

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

  1. Изолированный контекст Каждый iFrame содержит собственный document и window. Попытка напрямую позиционировать элемент из родительского документа относительно элемента внутри iFrame или наоборот приведет к ошибкам, если не учитывать этот изолированный контекст.

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

Создание Popper.js для элементов внутри iFrame

Для корректного позиционирования необходимо учитывать родительский контейнер iFrame:

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

// Элемент внутри iFrame
const iframe = document.querySelector('#my-iframe');
const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
const reference = iframeDocument.querySelector('#reference');
const tooltip = iframeDocument.createElement('div');

iframeDocument.body.appendChild(tooltip);

createPopper(reference, tooltip, {
  placement: 'top',
  modifiers: [
    {
      name: 'offset',
      options: {
        offset: [0, 8],
      },
    },
  ],
});

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

  • Использование iframe.contentDocument или iframe.contentWindow.document для доступа к DOM внутри iFrame.
  • Создание Popper.js внутри того же документа, где находится целевой элемент.
  • Применение стандартных модификаторов (offset, flip, preventOverflow) работает без изменений, если Popper создается внутри iFrame.

Позиционирование элементов родительского документа относительно iFrame

Если требуется позиционировать элемент в родительском документе относительно элемента внутри iFrame, необходимо учитывать смещение iFrame и прокрутку страницы:

const iframe = document.querySelector('#my-iframe');
const iframeRect = iframe.getBoundingClientRect();
const reference = iframe.contentDocument.querySelector('#reference');
const referenceRect = reference.getBoundingClientRect();

const tooltip = document.createElement('div');
document.body.appendChild(tooltip);

createPopper(
  {
    getBoundingClientRect: () => ({
      width: referenceRect.width,
      height: referenceRect.height,
      top: iframeRect.top + referenceRect.top,
      bottom: iframeRect.top + referenceRect.bottom,
      left: iframeRect.left + referenceRect.left,
      right: iframeRect.left + referenceRect.right,
    }),
    clientWidth: referenceRect.width,
    clientHeight: referenceRect.height,
  },
  tooltip,
  {
    placement: 'right',
  }
);

Важные моменты:

  • Использование кастомного объекта reference с методом getBoundingClientRect для Popper.js позволяет корректно вычислять позицию относительно родительского документа.
  • Сложение координат iFrame и элемента внутри него обеспечивает точное позиционирование.
  • Этот подход нужен только тогда, когда Popper создается в другом контексте документа, нежели целевой элемент.

Ограничения и проблемы

  1. Кросс-доменные iFrame Если iFrame загружает контент с другого домена, доступ к его contentDocument или contentWindow запрещен политикой CORS. В этом случае прямое позиционирование через Popper невозможно. Решением может быть обмен координатами через postMessage.

  2. Прокрутка iFrame Внутри iFrame может быть собственная прокрутка. Для корректного позиционирования нужно учитывать scrollTop и scrollLeft iFrame при вычислении координат.

  3. Перерисовка при изменении размеров Popper.js автоматически обновляет позицию элементов при изменении размеров окна браузера, но при работе с iFrame может потребоваться дополнительный вызов update() при изменении размеров iFrame или его содержимого.

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

  • Использовать position: absolute внутри iFrame для Popper-элементов, чтобы избежать конфликтов с CSS родительского документа.
  • Минимизировать количество вызовов update() при динамическом изменении размеров iFrame, используя requestAnimationFrame для оптимизации.
  • Для сложных и динамичных интерфейсов рассмотреть использование popperInstance.forceUpdate() вместо стандартного update(), если требуется мгновенная корректировка позиции.

Итоговое применение

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