Skip to content

Анимации ​

В CDS 4 у движения одна шкала: семь длительностей, набор кривых и два цикла. Она одинакова для всех тем (cds, b2b, promo, luma). Пользоваться ею можно четырьмя путями: готовые переходы Vue, SASS-модуль, CSS-переменные и JS-объект motion.

Берите ступени шкалы, а не литералы вроде 0.2s ease-in-out: так ваши анимации идут в одном темпе с компонентами CDS, а замена значения в шкале доходит до всех мест разом.

Шкала длительностей ​

Нажмите «Запустить»: маркеры проходят одинаковый путь, каждый за время своей ступени.

ИмяЗначениеДля чего
instant50msнажатие
very-fast100msвход в ховер, исчезновение поповера и галочки
fast200msвыход из ховера, смена цвета и выбора, появление
normal300msразмер крупного блока, появление модалки и дровера
slow400msраскрытие большого элемента
very-slow500msзатемнение фона
spring300msвремя, за которое пружина успокаивается

Циклы лежат вне шкалы: шкала отвечает на действие пользователя, а цикл идёт сам. Пульс длится 2s, рябь — 1.5s.

Кривые ​

Одна и та же длительность на разных кривых: enter влетает на скорости и тормозит, exit разгоняется и уходит, standard мягко трогается и мягко останавливается. Переключите длительность, чтобы сравнить кривые на коротком и длинном пути.

ИмяЗначениеДля чего
standardcubic-bezier(0.2, 0, 0, 1)по умолчанию: изменение на месте и движение
entercubic-bezier(0.05, 0.7, 0.1, 1)появление
exitcubic-bezier(0.3, 0, 1, 1)только уход
springlinear(…), пружина M3 fast-spatialпоявление мелкого объекта с отскоком
loopcubic-bezier(0.37, 0, 0.63, 1)цикл пульса
ripplecubic-bezier(0, 0, 0.2, 1)цикл ряби
linearlinearтаймер: шаг тика равен длительности

Отскок у spring уместен только в масштабе и положении. Подставляйте пружину в transform, а не в opacity или background-color: в цвете и прозрачности отскока не бывает.

Что выбрать ​

СлучайДлительностьКривая
нажатиеinstantstandard
вход в ховерvery-faststandard
выход из ховера, смена выбора, цветfaststandard
появление поповера, дропдауна, тостаfastenter
уход поповера, галочкиvery-fastexit
размер крупного блокаnormalstandard
модалка, дровер: появление / уходnormal/fastenter / exit
пульс / рябь2s / 1.5sloop / ripple

Готовые переходы ​

Переходы — компоненты-обёртки над <Transition> и <TransitionGroup> Vue: положите внутрь элемент с v-if или v-show, классы и стили не нужны. Выберите переход и скройте или покажите карточку:

Sandbox Storybook
ПереходЧто анимируетВремя и криваяКогда брать
CdsTransitionFadeпрозрачностьfast enter / very-fast exitпоявление и уход на месте: подсказки, подписи
CdsTransitionSlideYсдвиг на 8px по вертикали и прозрачностьfast enter / very-fast exitмелкие блоки, выпадающее содержимое
CdsTransitionSlideXсдвиг на 15px по горизонтали и прозрачностьfast enter / very-fast exitсмена содержимого по горизонтали
CdsTransitionSlideRightсдвиг на всю ширину вправо; уходящий блок выходит из потокаnormal standardперелистывание вперёд: календарь, карусель
CdsTransitionSlideLeftсдвиг на всю ширину влево; уходящий блок выходит из потокаnormal standardперелистывание назад
CdsTransitionJumpRightсдвиг на 15px вправо и прозрачность; уходящий блок выходит из потокаnormal standardсмена значения, которая «едет» вперёд: счётчики, подписи
CdsTransitionJumpLeftсдвиг на 15px влево и прозрачность; уходящий блок выходит из потокаnormal standardто же в обратную сторону
CdsTransitionScaleмасштаб от нуля и прозрачность; уход — только затуханиеfast enter / very-fast exitвсплывающие меню, метки
CdsTransitionExpandвысота (JS): раскрытие и схлопывание по вертикалиfast standardраскрывающиеся панели, аккордеон
CdsTransitionExpandXширина (JS): то же по горизонталиfast standardбоковые панели, сворачиваемые колонки
CdsTransitionPresenceзатухание, затем схлопывание высоты, отступов и рамкиуход very-fast exit + fast standard; появление fast standard + fast enterблок уходит из потока, соседи должны сдвинуться плавно; так раскрывается ветка CdsTree
CdsTransitionModalBottomвыезд снизу экранаnormal enter / fast exitпанель или окно, которое приходит снизу
CdsTransitionModalTopвыезд сверху экранаnormal enter / fast exitпанель или окно, которое приходит сверху
CdsModalTransitionраскрытие модалки из элемента-источника (проп target)normal enter / fast exit; содержимое normal×2 / fast×2 на standardмодалка, «вылетающая» из кнопки или карточки

Пропсы зависят от вида перехода:

  • CSS-переходы (Fade, Slide*, Jump*, Scale, Modal*): mode, group (список через TransitionGroup), disabled (без анимации), hideOnLeave (прячет уходящий элемент через display: none), leaveAbsolute (на время ухода вынимает элемент из потока) и origin (точка, от которой идёт масштаб).
  • JS-переходы (Expand, ExpandX): mode, disabled, group, hideOnLeave.
  • CdsTransitionPresence: appear, group, disabled — см. ниже.
  • CdsModalTransition: один проп target — элемент или координаты [x, y], из которых раскрывается модалка.

Все пропсы можно покрутить в Storybook, в истории «Песочница: переходы».

Что общего у переходов:

  • Уходящий элемент, вынутый из потока (SlideRight, SlideLeft, Jump* и любой CSS-переход с leaveAbsolute), не принимает клики: клик не попадёт в то, что уже уходит.
  • При prefers-reduced-motion сдвиг и масштаб заменяются затуханием. У Expand и ExpandX размер при reduced-motion меняется сразу. У JS-переходов (Expand, ExpandX, CdsModalTransition) режим читается при монтировании, а disabled по умолчанию следует за ним.

CdsTransitionPresence: уход блока из потока ​

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

Sandbox Storybook
  1. Уход. Затухание за very-fast на кривой exit, затем схлопывание за fast на standard. Вместе с высотой схлопываются вертикальные отступы и рамка, промежутка не остаётся.
  2. Появление идёт наоборот: раскрытие за fast на standard, затем проявление за fast на enter.
  3. При prefers-reduced-motion остаётся только затухание, место закрывается и открывается сразу.

Так раскрываются ветки CdsTree и уходит CdsNotification. Отступ между блоками списка задавайте margin, а не gap у контейнера: margin схлопывается вместе с блоком, а gap исчезает рывком в конце ухода.

ПропсЧто делает
appearиграть переход при первом показе
groupиспользовать TransitionGroup — для списка элементов
disabledотключить анимацию: элементы появляются и исчезают сразу

Остальные атрибуты Transition и TransitionGroup (например, tag у списка) передаются дальше. @after-leave срабатывает после обеих фаз — по нему удобно отправлять событие закрытия:

vue
<CdsTransitionPresence @after-leave="emit('closed')">
  <div v-if="visible" class="alert">Сохранено</div>
</CdsTransitionPresence>

Слушатели хуков (@enter, @after-leave и других) получают только элемент.

Пульс и рябь ​

Пульс — сигнал «идёт сейчас»: точка дышит прозрачностью. Рябь — сигнал «появилось новое»: кольцо под точкой растёт вдвое и гаснет. Оба цикла включаются точечно и при prefers-reduced-motion не запускаются. Готовый пример сигнального цикла — CdsStatus с пропсами indicator и pulse: точка у статуса дышит, пока процесс идёт. Пульс бесконечный, поэтому по WCAG 2.2.2 включайте его только пока сигнал актуален.

Sandbox Storybook

В SCSS те же циклы дают миксины, ключевые кадры они объявляют сами:

scss
@use '@central-design-system/components/styles/motion';

.status-dot {
  @include motion.pulse;
}

.status-dot__ring {
  @include motion.ripple;
}

Reduced-motion ​

При prefers-reduced-motion: reduce выключаются сдвиг, масштаб и пружины. Прозрачность и цвет остаются: обратная связь состояний не пропадает, а смена цвета не вызывает вестибулярного дискомфорта.

Циклы делятся на два класса:

  • Декоративный или сигнальный — пульс и рябь, шиммер CdsSkeletonLoader. Выключается: статус читается и без анимации, а бесконечное мигание — именно то, от чего такой пользователь просит избавить.
  • Индикатор процесса — CdsLoader, неопределённый CdsProgressBar. Продолжает работать: неподвижный спиннер читается как зависание. WCAG 2.2.2 относит такое движение к необходимому («essential»).

Свой индикатор процесса пишите без spatial. Этой политике следуют и компоненты ДС: шиммер скелетона при reduced-motion выключен, а CdsLoader и неопределённый CdsProgressBar продолжают крутиться.

Проверить поведение без смены системных настроек можно в Chrome DevTools: меню ⋮ → More tools → Rendering → Emulate CSS media feature prefers-reduced-motion → reduce. Демо на этой странице сразу переключатся.

Если вы подключаете легаси-сброс ($legacy-reset: true), политика reduced-motion в нём не навязывается: сброс совместимости с разметкой CDS 3 не глушит анимации.

Справочник ​

Значения в SASS-модуле, CSS-переменных и motion совпадают — это проверяет тест.

SASS-модуль ​

Модуль подключается отдельным входом и ничего не тянет за собой, поэтому годится для любого SCSS проекта. Функции возвращают готовые значения, а не var(): с ними можно считать в SASS (складывать задержки, суммировать фазы).

scss
@use '@central-design-system/components/styles/motion';
ВызовЧто возвращает
motion.duration(<имя>)длительность ступени, например 200ms
motion.easing(<имя>)кривую, например cubic-bezier(0.2, 0, 0, 1)
motion.transition(<свойства>, <длительность>, <кривая>, <задержка>)список для transition: каждое свойство с одной ступенью
motion.interactive-transition(<свойства>, <состояние>)ступень interactive для состояния rest, hover или active

Имя, которого нет в шкале, — ошибка компиляции со списком допустимых имён. Список свойств у transition обязателен: transition: all модуль не пишет.

Смена состояния. transition принимает свойства через пробел, длительность и кривую (по умолчанию standard). Несколько вызовов склеиваются через запятую:

scss
.tooltip {
  transition: motion.transition(opacity transform, fast, enter);

  &.is-leaving {
    transition: motion.transition(opacity, very-fast, exit);
  }
}

.handle {
  transition:
    motion.transition(left, fast),
    motion.transition(right, fast, $delay: very-fast);
}

Ховер и нажатие. @include motion.interactive(<свойства>) раскладывает ступени по состояниям: выход из ховера 200ms, вход 100ms, нажатие 50ms. Браузер берёт transition того состояния, в которое переходит элемент, поэтому ступени лежат на состояниях. Если ховер висит на предке, а переход — на потомке, возьмите ступень вручную:

scss
.chip {
  @include motion.interactive(background-color border-color color);
}

.card:hover .arrow {
  transition: motion.interactive-transition(transform, hover);
}

Масштаб и положение. Всё, что двигает или масштабирует, кладите в spatial: при reduced-motion оно выключается, а цвет и прозрачность остаются. Если сдвиг задаёт сама ДС и без замены он остался бы, пару составляет reduced — правила только для пользователей с reduced-motion:

scss
.badge {
  @include motion.spatial {
    animation: pop motion.duration(spring) motion.easing(spring);
  }
}

.drawer-content {
  transform: translateX(-16px);

  @include motion.reduced {
    transform: none;
  }
}

CSS-переменные ​

Те же значения лежат на :root — для обычного CSS, инлайновых стилей и Tailwind. Имена строятся по шкале: --cds-motion-duration-<имя>, --cds-motion-easing-<имя>, --cds-motion-loop-<имя> (имена — из таблиц выше). Смена темы переменные не меняет.

css
.toast {
  transition: opacity var(--cds-motion-duration-fast) var(--cds-motion-easing-enter);
}

JS-объект motion ​

Когда движением управляет скрипт — таймер, Web Animations API, расчёт задержки, — берите шкалу из motion. Длительности и циклы — числа в миллисекундах, кривые — строки CSS.

ts
import { motion } from '@central-design-system/components';

motion.durations.fast; // 200
motion.easings.enter; // 'cubic-bezier(0.05, 0.7, 0.1, 1)'
motion.loops.pulse; // 2000

el.animate([{ opacity: 0 }, { opacity: 1 }], {
  duration: motion.durations.fast,
  easing: motion.easings.enter
});