Анимации
В CDS 4 у движения одна шкала: семь длительностей, набор кривых и два цикла. Она одинакова для всех тем (cds, b2b, promo, luma). Пользоваться ею можно четырьмя путями: готовые переходы Vue, SASS-модуль, CSS-переменные и JS-объект motion.
Берите ступени шкалы, а не литералы вроде
0.2s ease-in-out: так ваши анимации идут в одном темпе с компонентами CDS, а замена значения в шкале доходит до всех мест разом.
Шкала длительностей
Нажмите «Запустить»: маркеры проходят одинаковый путь, каждый за время своей ступени.
| Имя | Значение | Для чего |
|---|---|---|
instant | 50ms | нажатие |
very-fast | 100ms | вход в ховер, исчезновение поповера и галочки |
fast | 200ms | выход из ховера, смена цвета и выбора, появление |
normal | 300ms | размер крупного блока, появление модалки и дровера |
slow | 400ms | раскрытие большого элемента |
very-slow | 500ms | затемнение фона |
spring | 300ms | время, за которое пружина успокаивается |
Циклы лежат вне шкалы: шкала отвечает на действие пользователя, а цикл идёт сам. Пульс длится 2s, рябь — 1.5s.
Кривые
Одна и та же длительность на разных кривых: enter влетает на скорости и тормозит, exit разгоняется и уходит, standard мягко трогается и мягко останавливается. Переключите длительность, чтобы сравнить кривые на коротком и длинном пути.
| Имя | Значение | Для чего |
|---|---|---|
standard | cubic-bezier(0.2, 0, 0, 1) | по умолчанию: изменение на месте и движение |
enter | cubic-bezier(0.05, 0.7, 0.1, 1) | появление |
exit | cubic-bezier(0.3, 0, 1, 1) | только уход |
spring | linear(…), пружина M3 fast-spatial | появление мелкого объекта с отскоком |
loop | cubic-bezier(0.37, 0, 0.63, 1) | цикл пульса |
ripple | cubic-bezier(0, 0, 0.2, 1) | цикл ряби |
linear | linear | таймер: шаг тика равен длительности |
Отскок у spring уместен только в масштабе и положении. Подставляйте пружину в transform, а не в opacity или background-color: в цвете и прозрачности отскока не бывает.
Что выбрать
| Случай | Длительность | Кривая |
|---|---|---|
| нажатие | instant | standard |
| вход в ховер | very-fast | standard |
| выход из ховера, смена выбора, цвет | fast | standard |
| появление поповера, дропдауна, тоста | fast | enter |
| уход поповера, галочки | very-fast | exit |
| размер крупного блока | normal | standard |
| модалка, дровер: появление / уход | normal/fast | enter / exit |
| пульс / рябь | 2s / 1.5s | loop / ripple |
Готовые переходы
Переходы — компоненты-обёртки над <Transition> и <TransitionGroup> Vue: положите внутрь элемент с v-if или v-show, классы и стили не нужны. Выберите переход и скройте или покажите карточку:
| Переход | Что анимирует | Время и кривая | Когда брать |
|---|---|---|---|
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 делает это в две фазы — добавьте и уберите несколько счетов:
- Уход. Затухание за
very-fastна кривойexit, затем схлопывание заfastнаstandard. Вместе с высотой схлопываются вертикальные отступы и рамка, промежутка не остаётся. - Появление идёт наоборот: раскрытие за
fastнаstandard, затем проявление заfastнаenter. - При
prefers-reduced-motionостаётся только затухание, место закрывается и открывается сразу.
Так раскрываются ветки CdsTree и уходит CdsNotification. Отступ между блоками списка задавайте margin, а не gap у контейнера: margin схлопывается вместе с блоком, а gap исчезает рывком в конце ухода.
| Пропс | Что делает |
|---|---|
appear | играть переход при первом показе |
group | использовать TransitionGroup — для списка элементов |
disabled | отключить анимацию: элементы появляются и исчезают сразу |
Остальные атрибуты Transition и TransitionGroup (например, tag у списка) передаются дальше. @after-leave срабатывает после обеих фаз — по нему удобно отправлять событие закрытия:
<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 включайте его только пока сигнал актуален.
В 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 (складывать задержки, суммировать фазы).
@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). Несколько вызовов склеиваются через запятую:
.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 того состояния, в которое переходит элемент, поэтому ступени лежат на состояниях. Если ховер висит на предке, а переход — на потомке, возьмите ступень вручную:
.chip {
@include motion.interactive(background-color border-color color);
}
.card:hover .arrow {
transition: motion.interactive-transition(transform, hover);
}Масштаб и положение. Всё, что двигает или масштабирует, кладите в spatial: при reduced-motion оно выключается, а цвет и прозрачность остаются. Если сдвиг задаёт сама ДС и без замены он остался бы, пару составляет reduced — правила только для пользователей с reduced-motion:
.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-<имя> (имена — из таблиц выше). Смена темы переменные не меняет.
.toast {
transition: opacity var(--cds-motion-duration-fast) var(--cds-motion-easing-enter);
}JS-объект motion
Когда движением управляет скрипт — таймер, Web Animations API, расчёт задержки, — берите шкалу из motion. Длительности и циклы — числа в миллисекундах, кривые — строки CSS.
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
});