Svelte интеграция

Для интеграции Floating UI в проект на Svelte сначала необходимо установить пакет. Основной пакет для позиционирования элементов — @floating-ui/dom, но для использования в реактивной среде Svelte часто удобнее использовать обертку, которая управляет состоянием позиции через реактивные переменные.

npm install @floating-ui/dom

Импорт основных функций:

import { computePosition, offset, flip, shift } from '@floating-ui/dom';

computePosition — центральная функция библиотеки, которая рассчитывает позицию «флоатинга» относительно целевого элемента. offset, flip и shift — middleware для управления смещением, отражением при выходе за границы экрана и подстройкой позиции.

Реактивное позиционирование элементов

В Svelte позиция часто хранится в реактивных переменных:

<script>
  import { onMount } from 'svelte';
  import { computePosition, offset, flip, shift } from '@floating-ui/dom';

  let referenceEl;
  let floatingEl;
  let x = 0;
  let y = 0;

  function updatePosition() {
    if (referenceEl && floatingEl) {
      computePosition(referenceEl, floatingEl, {
        placement: 'bottom-start',
        middleware: [offset(8), flip(), shift({ padding: 5 })]
      }).then(({ x: newX, y: newY }) => {
        x = newX;
        y = newY;
      });
    }
  }

  onMount(() => {
    const observer = new ResizeObserver(updatePosition);
    observer.observe(referenceEl);
    observer.observe(floatingEl);

    window.addEventListener('scroll', updatePosition);
    window.addEventListener('resize', updatePosition);

    updatePosition();

    return () => {
      observer.disconnect();
      window.removeEventListener('scroll', updatePosition);
      window.removeEventListener('resize', updatePosition);
    };
  });
</script>

<div bind:this={referenceEl}>Триггер</div>
<div
  bind:this={floatingEl}
  style="position: absolute; top: {y}px; left: {x}px;"
>
  Всплывающий элемент
</div>

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

  • bind:this позволяет получить ссылку на DOM-элемент.
  • ResizeObserver отслеживает изменения размеров элементов, чтобы позиция оставалась корректной.
  • scroll и resize события на window гарантируют реактивное обновление при прокрутке или изменении размеров окна.

Использование middleware для точного позиционирования

Middleware позволяют модифицировать финальные координаты:

  • offset(value) — задает отступ от опорного элемента.
  • flip() — автоматически переворачивает позицию при нехватке места.
  • shift(options) — смещает элемент внутри видимой области с учетом padding.
  • arrow({ element }) — обеспечивает корректное позиционирование стрелки на всплывающем элементе.

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

<script>
  import { arrow } from '@floating-ui/dom';

  let arrowEl;

  function updatePosition() {
    computePosition(referenceEl, floatingEl, {
      placement: 'top',
      middleware: [
        offset(10),
        flip(),
        shift({ padding: 5 }),
        arrow({ element: arrowEl })
      ]
    }).then(({ x: newX, y: newY, middlewareData }) => {
      x = newX;
      y = newY;
      arrowX = middlewareData.arrow?.x ?? 0;
      arrowY = middlewareData.arrow?.y ?? 0;
    });
  }
</script>

<div bind:this={floatingEl} style="position:absolute; top:{y}px; left:{x}px;">
  Всплывающий элемент
  <div bind:this={arrowEl} style="position:absolute; left:{arrowX}px; top:{arrowY}px;">
    ▼
  </div>
</div>

Интеграция с Svelte transitions и анимацией

Floating UI прекрасно сочетается с переходами и анимациями Svelte. Для плавного появления/исчезновения можно использовать transition:fade:

<script>
  import { fade } from 'svelte/transition';
  let visible = false;
</script>

<button on:click={() => visible = !visible}>Toggle</button>

{#if visible}
  <div
    bind:this={floatingEl}
    style="position:absolute; top:{y}px; left:{x}px;"
    transition:fade
  >
    Появляющийся элемент
  </div>
{/if}

Совет: вызов updatePosition() следует делать после появления элемента, чтобы координаты были рассчитаны уже для существующего DOM.

Реактивные хранилища для управления состоянием

Для сложных интерфейсов удобно хранить состояние позиции во внешнем store:

import { writable } from 'svelte/store';

export const floatingPosition = writable({ x: 0, y: 0 });

function updatePosition(referenceEl, floatingEl) {
  computePosition(referenceEl, floatingEl, {
    placement: 'bottom',
    middleware: [offset(8), flip(), shift({ padding: 5 })]
  }).then(({ x, y }) => floatingPosition.set({ x, y }));
}

Компоненты могут подписываться на floatingPosition и автоматически обновлять стили:

<div bind:this={floatingEl} style="position:absolute; top:{ $floatingPosition.y }px; left:{ $floatingPosition.x }px;">
  Контент
</div>

Использование Floating UI с динамическим контентом

Если содержимое всплывающего блока изменяется динамически, важно повторно вызывать computePosition. Для этого удобно использовать Svelte reactive statements:

$: updatePosition();

Эта конструкция автоматически пересчитает позицию при изменении зависимых переменных, например размеров элементов или состояния видимости.

Интеграция с кастомными компонентами

Floating UI можно применять внутри сложных компонентных структур:

  • Меню, тултипы, модальные окна.
  • Компоненты с анимацией появления.
  • Контекстные подсказки для интерактивных элементов.

При этом следует:

  • Использовать bind:this для всех опорных и всплывающих элементов.
  • Подключать middleware для предотвращения выхода за границы viewport.
  • Обновлять позицию после изменений DOM, используя onMount и реактивные блоки.

Эта стратегия обеспечивает корректное позиционирование и позволяет полностью раскрыть возможности Floating UI в среде Svelte.