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"> для всего приложения.
Это упрощает управление модальными окнами и всплывающими
подсказками.Portal компоненты в SvelteKit обеспечивают мощный инструмент для управления UI-слоями, позволяя создавать модальные окна, тултипы, уведомления и другие элементы интерфейса с полной независимостью от структуры родительских компонентов и ограничений CSS.