Portal компоненты

Portal компоненты позволяют рендерить содержимое в DOM в месте, отличном от его логического родителя. В контексте SvelteKit это особенно полезно для модальных окон, всплывающих подсказок, тултипов и других элементов интерфейса, которые должны отображаться поверх остальной структуры приложения, без влияния CSS-ограничений родительского контейнера.

Основная концепция

В SvelteKit Portal реализуется через механизм “teleport” содержимого в другой узел DOM. Обычно создается специальный контейнер, куда будут “портироваться” элементы:

<div id="portal-root"></div>

В компоненте используется API для переноса содержимого:

<script>
  import { onMount, onDestroy } from 'svelte';
  let portalTarget;

  onMount(() => {
    portalTarget = document.getElementById('portal-root');
  });
</script>

{#if portalTarget}
  <svelte:portal this={portalTarget}>
    <div class="modal">
      Контент модального окна
    </div>
  </svelte:portal>
{/if}

Ключевой момент: <svelte:portal> создаёт отдельный рендер-путь, который не зависит от CSS и структуры родителя.

Управление жизненным циклом порталов

Важно учитывать, что содержимое портала должно корректно монтироваться и размонтироваться вместе с компонентом. Для этого используются стандартные хуки onMount и onDestroy:

<script>
  import { onMount, onDestroy } from 'svelte';
  let portalNode;

  onMount(() => {
    portalNode = document.createElement('div');
    portalNode.classList.add('tooltip-container');
    document.body.appendChild(portalNode);
  });

  onDestroy(() => {
    document.body.removeChild(portalNode);
  });
</script>

<svelte:fragment this={portalNode}>
  <div class="tooltip">
    Всплывающая подсказка
  </div>
</svelte:fragment>

Использование svelte:fragment позволяет рендерить элементы напрямую в выбранный узел DOM без необходимости создания отдельного компонента.

Стилизация и взаимодействие

Поскольку содержимое портала отделено от логического родителя, стандартные CSS-ограничения, такие как overflow: hidden или z-index, не применяются. Это обеспечивает независимую позиционировку:

.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1000;
  background: white;
  padding: 2rem;
  border-radius: 8px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
}

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

<script>
  let tooltipX = 0;
  let tooltipY = 0;

  function showTooltip(event) {
    tooltipX = event.clientX + 10;
    tooltipY = event.clientY + 10;
  }
</script>

<div on:mousemove={showTooltip}>
  Наведи на меня
</div>

{#if tooltipX && tooltipY}
  <svelte:fragment this={portalNode}>
    <div class="tooltip" style="top:{tooltipY}px; left:{tooltipX}px; position:absolute;">
      Подсказка
    </div>
  </svelte:fragment>
{/if}

Комбинирование с анимациями

Portal компоненты легко интегрируются с анимациями Svelte, такими как transition:fade или animate:flip. Анимация применяется к портированному элементу без влияния на остальную структуру:

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

<button on:click={() => show = !show}>Открыть модалку</button>

{#if show}
  <svelte:fragment this={portalNode} transition:fade={{ duration: 300 }}>
    <div class="modal">
      Модальное окно с анимацией
    </div>
  </svelte:fragment>
{/if}

Практические рекомендации

  • Единый контейнер для порталов: создать один корневой <div id="portal-root"> для всего приложения. Это упрощает управление модальными окнами и всплывающими подсказками.
  • Очистка ресурсов: при размонтировании компонента всегда удалять узлы из DOM, чтобы избежать утечек памяти.
  • Динамическая позиция: для интерактивных элементов (тултипы, popovers) координаты следует рассчитывать при каждом событии взаимодействия.
  • Стилизация с z-index: использовать уникальные значения z-index для разных типов порталов, чтобы предотвратить перекрытия.

Portal компоненты в SvelteKit обеспечивают мощный инструмент для управления UI-слоями, позволяя создавать модальные окна, тултипы, уведомления и другие элементы интерфейса с полной независимостью от структуры родительских компонентов и ограничений CSS.