Context Menu

Context Menu — это пользовательский интерфейс, позволяющий предоставлять интерактивные опции при клике правой кнопкой мыши на элементе. В SvelteKit его реализация опирается на реактивность фреймворка и возможности DOM для обработки событий. Важной особенностью является динамическое управление состоянием видимости и позиции меню.

Управление состоянием

Для отображения контекстного меню требуется хранить несколько ключевых состояний:

<script>
  import { writable } from 'svelte/store';

  const menuVisible = writable(false);
  const menuPosition = writable({ x: 0, y: 0 });

  function openMenu(event) {
    event.preventDefault();
    menuPosition.set({ x: event.clientX, y: event.clientY });
    menuVisible.set(true);
  }

  function closeMenu() {
    menuVisible.set(false);
  }
</script>
  • menuVisible — булевый флаг, определяющий видимость меню.
  • menuPosition — объект с координатами, где должно появляться меню.
  • openMenu — обработчик события contextmenu, предотвращающий стандартное поведение браузера и задающий позицию меню.
  • closeMenu — функция скрытия меню при клике вне его области или выборе пункта.

Разметка и привязка событий

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

<div on:contextmenu={openMenu} class="target-element">
  Правый клик на этом элементе
</div>

{#if $menuVisible}
  <ul class="context-menu" style="top: {$menuPosition.y}px; left: {$menuPosition.x}px;">
    <li on:click={() => { console.log('Option 1'); closeMenu(); }}>Option 1</li>
    <li on:click={() => { console.log('Option 2'); closeMenu(); }}>Option 2</li>
    <li on:click={() => { console.log('Option 3'); closeMenu(); }}>Option 3</li>
  </ul>
{/if}
  • Использование {#if $menuVisible} обеспечивает реактивное отображение меню только при необходимости.
  • Привязка стилей через style="top: {$menuPosition.y}px; left: {$menuPosition.x}px;" позволяет меню появляться в точке клика.
  • Каждый пункт меню содержит собственный обработчик on:click, который выполняет действие и закрывает меню.

Стилизация и адаптивность

Контекстное меню должно быть визуально отделено от контента и корректно отображаться на разных разрешениях экрана:

.context-menu {
  position: fixed;
  background-color: #ffffff;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-shadow: 0 4px 10px rgba(0,0,0,0.1);
  list-style: none;
  padding: 8px 0;
  margin: 0;
  z-index: 1000;
  min-width: 150px;
}

.context-menu li {
  padding: 8px 16px;
  cursor: pointer;
}

.context-menu li:hover {
  background-color: #f0f0f0;
}
  • position: fixed гарантирует, что меню не будет смещаться вместе с прокруткой.
  • Эффект hover улучшает визуальную интерактивность.
  • Минимальная ширина и паддинги делают элементы читаемыми и удобными для клика.

Динамическое формирование пунктов меню

В SvelteKit можно использовать реактивные массивы для генерации пунктов меню:

<script>
  const menuItems = [
    { label: 'Cut', action: () => console.log('Cut') },
    { label: 'Copy', action: () => console.log('Copy') },
    { label: 'Paste', action: () => console.log('Paste') },
  ];
</script>

{#if $menuVisible}
  <ul class="context-menu" style="top: {$menuPosition.y}px; left: {$menuPosition.x}px;">
    {#each menuItems as item}
      <li on:click={() => { item.action(); closeMenu(); }}>{item.label}</li>
    {/each}
  </ul>
{/if}
  • Использование {#each} позволяет легко добавлять, удалять или менять пункты меню без изменения структуры разметки.
  • Каждое действие привязывается напрямую к объекту, что упрощает поддержку и расширение функциональности.

Обработка клика вне меню

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

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

  onMount(() => {
    const handleClickOutside = (event) => {
      const menu = document.querySelector('.context-menu');
      if (menu && !menu.contains(event.target)) {
        closeMenu();
      }
    };
    document.addEventListener('click', handleClickOutside);
    onDestroy(() => {
      document.removeEventListener('click', handleClickOutside);
    });
  });
</script>
  • Использование onMount гарантирует регистрацию события после рендера компонента.
  • Проверка !menu.contains(event.target) определяет, был ли клик вне меню.
  • Очистка события через onDestroy предотвращает утечки памяти.

Расширенные возможности

  • Подменю: можно добавлять вложенные списки, используя вложенные компоненты Svelte.
  • Контекстная логика: пункты меню могут меняться в зависимости от состояния приложения или прав пользователя.
  • Анимация появления: SvelteKit поддерживает transitions, например fade или slide, для плавного отображения меню.
<script>
  import { fade } from 'svelte/transition';
</script>

{#if $menuVisible}
  <ul class="context-menu" style="top: {$menuPosition.y}px; left: {$menuPosition.x}px;" transition:fade>
    {#each menuItems as item}
      <li on:click={() => { item.action(); closeMenu(); }}>{item.label}</li>
    {/each}
  </ul>
{/if}

Использование fade делает взаимодействие с контекстным меню более естественным и современным, повышая UX приложения.

Оптимизация производительности

  • Ограничение количества реактивных вычислений при перемещении мыши и повторном открытии меню.
  • Динамическая генерация меню только при вызове, чтобы не загружать DOM лишними элементами.
  • Минимизация вложенных компонентов для больших списков меню, что улучшает рендер и отзывчивость интерфейса.

Context Menu в SvelteKit сочетает простоту реактивного фреймворка с гибкостью кастомизации. Управление состоянием через store, динамическое создание пунктов и интеграция с DOM событиями создают мощный инструмент для интерактивных веб-интерфейсов.