Таймеры автозакрытия

В библиотеке Radix UI многие интерактивные компоненты, такие как Tooltip, DropdownMenu, Popover или HoverCard, поддерживают концепцию автозакрытия. Это механизм, позволяющий компоненту автоматически закрываться после определённого события или по истечении заданного времени. Автозакрытие повышает удобство взаимодействия с интерфейсом и предотвращает зависание открытых элементов на странице.

Ключевым элементом реализации автозакрытия является таймер, управляемый через состояние компонента и соответствующие пропсы.


Управление временем автозакрытия

Radix UI предоставляет пропсы, через которые можно контролировать задержку закрытия компонента:

  • delayDuration – определяет время в миллисекундах перед закрытием.
  • disableOutsidePointerEvents – блокирует события клика вне компонента до завершения задержки.

Пример для Tooltip:

import * as Tooltip from '@radix-ui/react-tooltip';

<Tooltip.Root delayDuration={500}>
  <Tooltip.Trigger>Наведи курсор</Tooltip.Trigger>
  <Tooltip.Content>
    Всплывающая подсказка закроется через 500 мс после ухода курсора
  </Tooltip.Content>
</Tooltip.Root>

В этом примере таймер срабатывает после ухода указателя с триггера, отсчитывая 500 миллисекунд, прежде чем скрыть содержимое.


Использование функций обратного вызова

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

  • onOpenChange – вызывается при изменении состояния компонента.
  • onCloseAuto – специфично для случаев автозакрытия (в HoverCard или Popover).

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

<Popover.Root
  onOpenCha nge={(isOpen) => {
    if (!isOpen) {
      console.log('Popover закрылся автоматически');
    }
  }}
>
  <Popover.Trigger>Открыть Popover</Popover.Trigger>
  <Popover.Content>
    Контент Popover
  </Popover.Content>
</Popover.Root>

Динамическая настройка таймера

Часто требуется менять время автозакрытия в зависимости от состояния приложения. Это можно реализовать с помощью состояния React:

import React, { useState } from 'react';
import * as Tooltip from '@radix-ui/react-tooltip';

function DynamicTooltip() {
  const [delay, setDelay] = useState(300);

  return (
    <div>
      <input
        type="number"
        value={delay}
        onCha nge={(e) => setDelay(Number(e.target.value))}
        placeholder="Установите задержку в мс"
      />
      <Tooltip.Root delayDuration={delay}>
        <Tooltip.Trigger>Наведи курсор</Tooltip.Trigger>
        <Tooltip.Content>Таймер автозакрытия изменяемый</Tooltip.Content>
      </Tooltip.Root>
    </div>
  );
}

Такой подход позволяет создавать адаптивные интерфейсы с разной динамикой закрытия всплывающих элементов.


Синхронизация нескольких таймеров

Если на странице присутствуют несколько элементов с автозакрытием, необходимо избегать конфликтов таймеров. Radix UI управляет этим через внутренние хук-таймеры и подписки на события, но при кастомизации можно использовать useEffect для управления несколькими состояниями одновременно:

import { useEffect, useState } from 'react';

function MultiTooltipManager() {
  const [openTooltip, setOpenTooltip] = useState(null);

  useEffect(() => {
    if (openTooltip !== null) {
      const timer = setTimeout(() => setOpenTooltip(null), 400);
      return () => clearTimeout(timer);
    }
  }, [openTooltip]);

  return (
    <>
      <button onMouseEn ter={() => setOpenTooltip(1)}>Tooltip 1</button>
      <button onMouseEn ter={() => setOpenTooltip(2)}>Tooltip 2</button>
    </>
  );
}

Таким образом, каждый таймер отслеживается отдельно, предотвращая случайное одновременное закрытие нескольких всплывающих элементов.


Продвинутая оптимизация автозакрытия

  • Прерывание таймера: таймер может быть остановлен при возвращении указателя на компонент (onPointerEnter) или при повторном открытии.
  • Увеличение UX: минимальная задержка позволяет пользователю успеть взаимодействовать с компонентом, не создавая ощущение «резкого исчезновения».
  • Комбинация с анимацией: таймеры лучше синхронизировать с анимациями открытия/закрытия, чтобы контент исчезал плавно.

Пример интеграции с анимацией framer-motion:

import { motion } from 'framer-motion';
import * as Tooltip from '@radix-ui/react-tooltip';

<Tooltip.Root delayDuration={300}>
  <Tooltip.Trigger>Hover me</Tooltip.Trigger>
  <Tooltip.Content asChild>
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      transition={{ duration: 0.2 }}
    >
      Контент с плавным автозакрытием
    </motion.div>
  </Tooltip.Content>
</Tooltip.Root>

Здесь таймер автозакрытия запускается одновременно с анимацией исчезновения, обеспечивая согласованное поведение интерфейса.


Рекомендации по использованию

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

Эффективное применение таймеров автозакрытия повышает отзывчивость интерфейса и обеспечивает согласованное поведение интерактивных элементов Radix UI.