Как создать всплывающее окно в Svelte без каких-либо других библиотек или зависимостей.
Я реализовал UI-компоненты своего последнего проекта Papyrs без сторонних библиотек дизайн-системы — т.е. создал все компоненты с нуля. Я сделал это, чтобы получить полный контроль и гибкость над разными блоками моего самоуверенного макета.
В этом сообщении в блоге я рассказываю, как вы можете разработать компонент всплывающего окна в Svelte.
Скелет
Всплывающее окно — это плавающий контейнер, отображаемый поверх содержимого рядом с якорем — обычно кнопкой, — которая инициирует его отображение. Чтобы улучшить визуальную фокусировку наложения, фон обычно используется для частичного затемнения вида за ним.
Мы можем начать реализацию с репликации приведенного выше скелета в компоненте с именем Popover.svelte
, который содержит button
и div
.
<button>Open</button> <div role="dialog" aria-labelledby="Title" aria-describedby="Description" aria-orientation="vertical" > <div>Backdrop</div> <div>Content</div> </div>
Чтобы улучшить доступность, мы можем установить роль dialog
и предоставить некоторую информацию aria
(см. Документацию MDN для более подробной информации).
Анимация
Мы создаем состояние boolean
— visible
— для отображения или закрытия всплывающего окна. При нажатии на button
состояние устанавливается на true
и выполняется рендеринг наложения. Наоборот, при нажатии на фон он превращается в false
и закрывается.
Кроме того, мы добавляем прослушиватель кликов во всплывающем окне, который ничего не делает, кроме как останавливает распространение события. Это полезно, чтобы избежать закрытия оверлея, когда пользователь взаимодействует с его содержимым.
Мы также можем заставить оверлей появляться и исчезать изящно благодаря директиве перехода, также известной как черная магия Svelte 😁.
<script lang="ts"> import { fade, scale } from 'svelte/transition'; import { quintOut } from 'svelte/easing'; let visible = false; </script> <button on:click={() => (visible = true)}>Open</button> {#if visible} <div role="dialog" aria-labelledby="Title" aria-describedby="Description" aria-orientation="vertical" transition:fade on:click|stopPropagation > <div on:click|stopPropagation={() => (visible = false)} transition:scale={{ delay: 25, duration: 150, easing: quintOut }} > Backdrop </div> <div>Content</div> </div> {/if}
Позиция над контентом
Всплывающее окно должно отображаться над всем содержимым независимо от того, прокручивалась страница или нет. Поэтому мы можем использовать позицию fixed
в качестве отправной точки. Его содержимое и фон имеют позиционирование absolute
. Фон также должен закрывать экран, но является дочерним элементом оверлея — следовательно, «абсолютным» — и контент должен располагаться рядом с якорем.
Остальной код CSS, который мы добавляем в наше решение, представляет собой минимальные настройки стиля для ширины, высоты или цвета.
<script lang="ts"> import { fade, scale } from 'svelte/transition'; import { quintOut } from 'svelte/easing'; let visible = false; </script> <button on:click={() => (visible = true)}>Open</button> {#if visible} <div role="dialog" aria-labelledby="Title" aria-describedby="Description" aria-orientation="vertical" transition:fade class="popover" on:click|stopPropagation > <div on:click|stopPropagation={() => (visible = false)} transition:scale={{ delay: 25, duration: 150, easing: quintOut }} class="backdrop" /> <div class="wrapper">Content</div> </div> {/if} <style> .popover { position: fixed; inset: 0; z-index: 997; } .backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.3); } .wrapper { position: absolute; min-width: 200px; max-width: 200px; min-height: 100px; width: fit-content; height: auto; overflow: hidden; display: flex; flex-direction: column; align-items: flex-start; background: white; color: black; } </style>
Позиция рядом с якорем
Чтобы установить наложение рядом с кнопкой, мы должны получить ссылку на этот элемент, чтобы найти его положение в окне просмотра. Для этой цели мы можем bind
якорь.
Когда ссылка готова или размер окна изменен (позиция может измениться, если пользователь изменит размер браузера), мы используем метод getBoundingClientRect() для запроса информации о позиции.
В конечном итоге мы переводим эту информацию JavaScript в переменные CSS, чтобы отобразить содержимое всплывающего окна именно в той позиции, которую мы хотели бы установить.
<script lang="ts"> // ... let anchor: HTMLButtonElement | undefined = undefined; let bottom: number; let left: number; const initPosition = () => ({ bottom, left } = anchor?.getBoundingClientRect() ?? { bottom: 0, left: 0 }); $: anchor, initPosition(); </script> <svelte:window on:resize={initPosition} /> <button on:click={() => (visible = true)} bind:this={anchor}>Open</button> {#if visible} <div role="dialog" aria-labelledby="Title" aria-describedby="Description" aria-orientation="vertical" transition:fade class="popover" on:click|stopPropagation style="--popover-top: {`${bottom}px`}; --popover-left: {`${left}px`}" > <!-- ... --> </div> {/if} <style> /** ... */ .wrapper { position: absolute; top: calc(var(--popover-top) + 10px); left: var(--popover-left); /** ... */ } </style>
Фрагмент кода выше был обрезан, чтобы показать только то, что относится к этой главе. В целом код компонента выглядит следующим образом:
<script lang="ts"> import { fade, scale } from 'svelte/transition'; import { quintOut } from 'svelte/easing'; let visible = false; let anchor: HTMLButtonElement | undefined = undefined; let bottom: number; let left: number; const initPosition = () => ({ bottom, left } = anchor?.getBoundingClientRect() ?? { bottom: 0, left: 0 }); $: anchor, initPosition(); </script> <svelte:window on:resize={initPosition} /> <button on:click={() => (visible = true)} bind:this={anchor}>Open</button> {#if visible} <div role="dialog" aria-labelledby="Title" aria-describedby="Description" aria-orientation="vertical" transition:fade class="popover" on:click|stopPropagation style="--popover-top: {`${bottom}px`}; --popover-left: {`${left}px`}" > <div on:click|stopPropagation={() => (visible = false)} transition:scale={{ delay: 25, duration: 150, easing: quintOut }} class="backdrop" /> <div class="wrapper">Content</div> </div> {/if} <style> .popover { position: fixed; inset: 0; z-index: 997; } .backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.3); } .wrapper { position: absolute; top: calc(var(--popover-top) + 10px); left: var(--popover-left); min-width: 200px; max-width: 200px; min-height: 100px; width: fit-content; height: auto; overflow: hidden; display: flex; flex-direction: column; align-items: flex-start; background: white; color: black; } </style>
Вот и все! Мы реализовали минимальное пользовательское всплывающее окно, которое можно использовать в любых приложениях Svelte без какой-либо зависимости.
Заключение
Мы не украшали решение, поэтому результат остается визуально грубым по краям, но всплывающее окно работает, как и ожидалось.
Чтобы продолжить, внедрить больше опций или сделать его блестящим, вы можете, например, взглянуть на открытый исходный код Papyrs на GitHub 🤗.
В бесконечность и дальше
Дэвид
Want to Connect? For more adventures, follow me on Twitter