Модальные окна и drawers

Модальные окна и drawers представляют собой интерфейсные компоненты для отображения дополнительного контента поверх основной страницы. Их ключевая цель — привлечение внимания пользователя к важной информации или взаимодействию без перехода на другую страницу. В SvelteKit их реализация отличается реактивностью и высокой интеграцией с компонентной моделью.


Создание модального окна

В SvelteKit модальные окна обычно реализуются через отдельный компонент, который управляет своим состоянием видимости. Стандартная структура выглядит следующим образом:

<script lang="ts">
  import { createEventDispatcher } from 'svelte';
  export let isOpen: boolean = false;

  const dispatch = createEventDispatcher();

  function closeModal() {
    isOpen = false;
    dispatch('close');
  }
</script>

{#if isOpen}
  <div class="modal-backdrop" on:click={closeModal}></div>
  <div class="modal">
    <slot></slot>
    <button on:click={closeModal}>Закрыть</button>
  </div>
{/if}

<style>
  .modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.5);
  }
  .modal {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: white;
    padding: 2rem;
    border-radius: 8px;
    z-index: 1000;
  }
</style>

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

  • Использование slot позволяет передавать внутрь модального окна любой контент.
  • createEventDispatcher обеспечивает связь с родительским компонентом, позволяя отслеживать закрытие модального окна.
  • Контейнер с затемнением (modal-backdrop) служит для блокировки взаимодействия с основной страницей и обработки клика вне модального окна.

Управление состоянием модального окна

В SvelteKit UI libs чаще всего используют реактивные переменные или сторы (writable) для управления видимостью:

<script lang="ts">
  import { writable } from 'svelte/store';
  import Modal from './Modal.svelte';

  const isModalOpen = writable(false);

  function openModal() {
    isModalOpen.set(true);
  }
</script>

<button on:click={openModal}>Открыть модальное окно</button>
<Modal bind:isOpen={$isModalOpen}>
  <h2>Заголовок модального окна</h2>
  <p>Содержимое модального окна</p>
</Modal>

Использование bind:isOpen обеспечивает двустороннюю реактивность между компонентом и стором.


Анимации и переходы

Для плавного появления и исчезновения модальных окон в SvelteKit применяются встроенные переходы:

<script lang="ts">
  import { fade, scale } from 'svelte/transition';
</script>

{#if isOpen}
  <div class="modal-backdrop" on:click={closeModal} transition:fade></div>
  <div class="modal" transition:scale>
    <slot></slot>
  </div>
{/if}
  • fade плавно изменяет прозрачность.
  • scale увеличивает или уменьшает компонент с эффектом масштабирования.

Drawers: боковые панели

Drawers — это панели, которые выезжают с одной из сторон экрана, чаще всего слева или справа, и используются для навигации или дополнительных опций.

<script lang="ts">
  import { writable } from 'svelte/store';
  const isDrawerOpen = writable(false);

  function toggleDrawer() {
    isDrawerOpen.update(value => !value);
  }
</script>

<button on:click={toggleDrawer}>Открыть Drawer</button>

{#if $isDrawerOpen}
  <div class="drawer-backdrop" on:click={toggleDrawer}></div>
  <aside class="drawer">
    <slot></slot>
  </aside>
{/if}

<style>
  .drawer-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.4);
    z-index: 900;
  }
  .drawer {
    position: fixed;
    top: 0;
    right: 0;
    width: 300px;
    height: 100%;
    background: white;
    padding: 1rem;
    box-shadow: -2px 0 8px rgba(0,0,0,0.2);
    z-index: 1000;
  }
</style>

Особенности:

  • Drawer может быть реализован как aside элемент, что улучшает семантику.
  • Backdrop блокирует взаимодействие с основной страницей.
  • Часто используется CSS-переход transform: translateX для эффекта выезда.

Управление событиями и доступность

Для модальных окон и drawers важно соблюдать доступность:

  • Использование aria-hidden, когда компонент закрыт.
  • Фокус должен переходить внутрь компонента при открытии и возвращаться к исходному элементу при закрытии.
  • Обработка клавиши Escape для закрытия:
<script lang="ts">
  function handleKeydown(event: KeyboardEvent) {
    if (event.key === 'Escape') closeModal();
  }
</script>

<div on:keydown={handleKeydown} tabindex="0">
  ...
</div>

Интеграция с сторонними UI-библиотеками

В SvelteKit есть несколько популярных UI libs, например Svelte Material UI (SMUI) или Skeleton UI, которые предоставляют готовые модальные компоненты и drawers с расширенной кастомизацией.

Пример использования SMUI Drawer:

<script lang="ts">
  import { Drawer, DrawerContent } from '@smui/drawer';
  let open = false;
</script>

<Drawer bind:open>
  <DrawerContent>
    <ul>
      <li>Пункт 1</li>
      <li>Пункт 2</li>
    </ul>
  </DrawerContent>
</Drawer>

<button on:click={() => open = !open}>Toggle Drawer</button>
  • Компоненты SMUI имеют встроенные стили и анимации.
  • bind:open обеспечивает реактивное управление видимостью.

Сложные сценарии

  • Вложенные модальные окна — открытие одного модального окна из другого требует корректного управления z-index и backdrop.
  • Многоуровневые drawers — часто используют несколько уровней навигации с разной шириной и overlay эффектами.
  • Асинхронный контент — модальные окна могут подгружать данные через load или fetch при открытии.

Эффективная реализация таких сценариев в SvelteKit требует строгого контроля реактивности, правильного использования сторов и событий, а также соблюдения доступности и UX стандартов.