Работа с анимациями

Radix UI предоставляет мощный набор компонентов для построения интерфейсов с доступностью по умолчанию. Работа с анимациями в Radix UI строится на принципе контролируемой визуальной трансформации компонентов, что позволяет плавно изменять состояние элементов без потери доступности и управления фокусом.

Анимации в Radix UI обычно применяются через CSS-переходы и Keyframe-анимации, используя вспомогательные утилиты вроде @radix-ui/react-animations или интеграцию с библиотеками стилей типа stitches или tailwindcss. Основной подход заключается в управлении состояниями компонентов (open, closed, active) и связывании этих состояний с соответствующими CSS-классами анимации.


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

У каждого компонента Radix UI есть управляемое состояние, которое можно использовать для запуска анимаций:

import * as Dialog from '@radix-ui/react-dialog';

<Dialog.Root>
  <Dialog.Trigger>Открыть диалог</Dialog.Trigger>
  <Dialog.Content className={isOpen ? 'fade-in' : 'fade-out'}>
    Содержимое диалога
  </Dialog.Content>
</Dialog.Root>

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


Keyframe-анимации для плавного перехода

Использование keyframes позволяет создавать сложные, последовательные анимации:

@keyframes slideIn {
  from { transform: translateY(-20px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

@keyframes slideOut {
  from { transform: translateY(0); opacity: 1; }
  to { transform: translateY(-20px); opacity: 0; }
}

.fade-in { animation: slideIn 0.3s ease-out forwards; }
.fade-out { animation: slideOut 0.2s ease-in forwards; }

Ключевой момент: использовать forwards для сохранения конечного состояния анимации, иначе элемент может вернуться к исходному стилю после завершения анимации.


Интеграция с CSS-in-JS

При работе с Radix UI часто применяется CSS-in-JS, например @stitches/react. Это позволяет динамически привязывать состояния компонентов к анимациям:

import { styled, keyframes } from '@stitches/react';
import * as Dialog from '@radix-ui/react-dialog';

const slideIn = keyframes({
  '0%': { transform: 'translateY(-20px)', opacity: 0 },
  '100%': { transform: 'translateY(0)', opacity: 1 },
});

const Content = styled(Dialog.Content, {
  animation: `${slideIn} 0.3s ease-out`,
});

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


Анимации появления и исчезновения

Для элементов с динамическим появлением (Dropdown, Popover, Dialog) рекомендуется использовать анимацию при монтировании и размонтировании:

import { keyframes, styled } from '@stitches/react';
import * as Popover from '@radix-ui/react-popover';

const fadeIn = keyframes({ from: { opacity: 0 }, to: { opacity: 1 } });
const fadeOut = keyframes({ from: { opacity: 1 }, to: { opacity: 0 } });

const StyledContent = styled(Popover.Content, {
  '&[data-state="open"]': { animation: `${fadeIn} 0.2s ease-out` },
  '&[data-state="closed"]': { animation: `${fadeOut} 0.2s ease-in` },
});

Особенность: Radix UI автоматически добавляет атрибут data-state, который отражает текущее состояние компонента (open или closed). Это упрощает связывание анимаций с состоянием без дополнительной логики.


Последовательные и составные анимации

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

const scaleUp = keyframes({ from: { transform: 'scale(0.95)' }, to: { transform: 'scale(1)' } });
const fadeIn = keyframes({ from: { opacity: 0 }, to: { opacity: 1 } });

const AnimatedContent = styled(Dialog.Content, {
  '&[data-state="open"]': {
    animation: `${scaleUp} 0.3s ease-out, ${fadeIn} 0.3s ease-out`,
  },
});

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


Управление задержками и синхронизацией

Для создания каскадных эффектов можно использовать CSS-переменные или утилиты stitches:

const fadeInDelay = keyframes({ from: { opacity: 0 }, to: { opacity: 1 } });

const Item = styled('div', {
  variants: {
    delay: {
      1: { animation: `${fadeInDelay} 0.3s ease-out 0.1s forwards` },
      2: { animation: `${fadeInDelay} 0.3s ease-out 0.2s forwards` },
      3: { animation: `${fadeInDelay} 0.3s ease-out 0.3s forwards` },
    },
  },
});

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


Анимации с использованием React Transition

Radix UI хорошо сочетается с библиотекой react-transition-group для контролируемых монтирования/размонтирования элементов с анимацией:

import { CSSTransition } from 'react-transition-group';
import * as Dialog from '@radix-ui/react-dialog';

<Dialog.Root>
  <Dialog.Trigger>Открыть</Dialog.Trigger>
  <CSSTransition in={isOpen} timeout={300} classNames="dialog">
    <Dialog.Content className="dialog-content">
      Содержимое
    </Dialog.Content>
  </CSSTransition>
</Dialog.Root>

CSSTransition позволяет сохранять доступность и управление фокусом Radix, одновременно добавляя полноценную анимацию монтирования и размонтирования.


Практические рекомендации

  • Использовать data-state для привязки анимаций к состояниям компонентов.
  • Сохранять доступность при анимации: избегать управления display: none без размонтирования компонента.
  • Комбинировать ключевые кадры и CSS-переходы для сложных эффектов.
  • Использовать CSS-in-JS для удобного динамического управления анимациями.
  • Для каскадных анимаций применять задержки через CSS-переменные или variants.
  • Проверять плавность на разных устройствах и браузерах, так как transform и opacity обычно оптимизированы для GPU-ускорения.

Эти подходы обеспечивают плавные, управляемые анимации в интерфейсах Radix UI без ущерба для доступности и стабильности компонентов.