Tooltip и popover — два близких по назначению, но различающихся по поведению интерфейсных паттерна. Оба используются для отображения дополнительной информации без перегрузки основного интерфейса.
В контексте SvelteKit UI-библиотек (например, Skeleton, Flowbite-Svelte, Melt UI) эти компоненты реализуются с учетом реактивности Svelte, доступности (a11y) и гибкости позиционирования.
В Svelte управление tooltip/popover обычно строится через локальное состояние:
<script>
let open = false;
</script>
<button
on:mouseenter={() => open = true}
on:mouseleave={() => open = false}
>
Hover me
</button>
{#if open}
<div class="tooltip">Подсказка</div>
{/if}
Ключевая идея — декларативное отображение через {#if} и
реактивные переменные.
Точное размещение всплывающего элемента относительно триггера — одна из главных задач. Используются:
position: absolute + вычисленияПример с вычислением координат:
<script>
import { onMount } from 'svelte';
let trigger;
let tooltip;
let style = '';
onMount(() => {
const rect = trigger.getBoundingClientRect();
style = `
top: ${rect.bottom + 8}px;
left: ${rect.left}px;
`;
});
</script>
<button bind:this={trigger}>Hover</button>
<div bind:this={tooltip} style={style}>
Tooltip
</div>
hover или focusmouseleave или blur<script>
let open = false;
let timeout;
function show() {
timeout = setTimeout(() => open = true, 200);
}
function hide() {
clearTimeout(timeout);
open = false;
}
</script>
<button on:mouseenter={show} on:mouseleave={hide}>
Hover
</button>
{#if open}
<div class="tooltip">Информация</div>
{/if}
Для tooltip важно:
aria-describedby<button aria-describedby="tip1">?</button>
<div id="tip1" role="tooltip">
Подсказка
</div>
Svelte предоставляет встроенные переходы:
<script>
import { fade } from 'svelte/transition';
</script>
{#if open}
<div transition:fade>
Tooltip
</div>
{/if}
<script>
let open = false;
</script>
<button on:click={() => open = !open}>
Open
</button>
{#if open}
<div class="popover">
Контент popover
</div>
{/if}
Используется обработчик на уровне документа:
<script>
import { onMount, onDestroy } from 'svelte';
let open = false;
let popover;
function handleClick(event) {
if (popover && !popover.contains(event.target)) {
open = false;
}
}
onMount(() => {
document.addEventListener('click', handleClick);
});
onDestroy(() => {
document.removeEventListener('click', handleClick);
});
</script>
<button on:click={() => open = !open}>Toggle</button>
{#if open}
<div bind:this={popover}>
Popover content
</div>
{/if}
Popover должен:
<script>
let popoverEl;
let buttonEl;
function openPopover() {
popoverEl?.focus();
}
</script>
<button bind:this={buttonEl} on:click={openPopover}>
Open
</button>
<div tabindex="0" bind:this={popoverEl}>
Content
</div>
Позволяет полностью контролировать разметку:
<script>
import { createPopover } from '@melt-ui/svelte';
const popover = createPopover();
</script>
<button use:popover.trigger>
Open
</button>
<div use:popover.content>
Content
</div>
Особенности:
Готовые стилизованные компоненты:
<script>
import { Tooltip } from 'flowbite-svelte';
</script>
<Tooltip text="Подсказка">
<button>Hover</button>
</Tooltip>
Плюсы:
Фокус на Tailwind и адаптивности:
<script>
import { Popup } from '@skeletonlabs/skeleton';
</script>
<Popup>
<button slot="trigger">Open</button>
<div slot="content">Content</div>
</Popup>
Продвинутые popover используют:
Пример логики:
if (rect.bottom + tooltipHeight > window.innerHeight) {
position = 'top';
}
Современные библиотеки делегируют это Floating UI.
Popover часто рендерится вне текущего DOM-дерева (в
<body>), чтобы:
В Svelte:
<svelte:body>
{#if open}
<div class="popover">...</div>
{/if}
</svelte:body>
Всплывающие элементы должны:
Типичная схема:
Важно учитывать:
Оптимизация:
{#if open}
<PopoverContent />
{/if}
Tooltip:
Popover:
В контексте маршрутизации:
import { writable } from 'svelte/store';
export const popoverOpen = writable(false);
Проверяются:
Инструменты:
Правильная реализация tooltip и popover в SvelteKit обеспечивает баланс между информативностью интерфейса и его чистотой, сохраняя производительность и доступность на высоком уровне.