Анимируй на композиторе через transform и opacity, выстраивай свои @keyframes и уважай предпочтения пользователей по движению.
Открыть этот урок в KodokonПрежде чем выбирать, что анимировать, вспомни конвейер отрисовки: layout (геометрия), paint (пиксели), composite (сборка слоёв на GPU). Анимация width или top запускает пересчёт layout на каждом кадре, а часто и перерисовку: это верный рецепт рывков ниже 60 кадров в секунду. transform и opacity обрабатываются напрямую композитором, без возврата к первым двум этапам. Профессиональное правило: по возможности анимируй только эти два свойства, а остальное имитируй (translate вместо top, scale вместо width).
.button {
transform: translateY(0);
transition:
transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1),
opacity 0.15s ease-out;
}
.button:hover {
transform: translateY(-2px);
}
.button:active {
transform: translateY(0) scale(0.97);
}Когда перехода уже не хватает (промежуточные шаги, зацикливание, автоматический старт), переходи к @keyframes. Две настройки отделяют аккуратную анимацию от визуального бага: animation-fill-mode управляет состоянием до старта (backwards применяет первый ключевой кадр во время задержки) и после окончания (forwards замораживает последний); both объединяет оба варианта. Учти также, что функция тайминга применяется между каждой парой ключевых кадров, а не ко всей длительности: её можно переопределить внутри ключевого кадра.
@keyframes slide-in {
from {
transform: translateX(-100%);
opacity: 0;
}
70% {
transform: translateX(8px);
opacity: 1;
}
to {
transform: translateX(0);
}
}
.toast {
animation: slide-in 0.35s ease-out both;
}Два дополнительных инструмента, которые стоит знать. will-change: transform заранее выносит элемент на отдельный слой, избегая запоздалой растеризации; но каждый слой стоит памяти GPU, поэтому включай его прямо перед анимацией и убирай после, а не оставляй постоянным стилем на десятках элементов. steps(n) заменяет непрерывную интерполяцию дискретными скачками: незаменимо для спрайтов, курсора в стиле печатной машинки или часов.
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}transform: translateX(100px), чем left: 100px?transform лучше поддерживается старыми браузерамиtransform обрабатывается композитором без пересчёта layout на каждом кадреleft работает только на абсолютно позиционированных элементах, а transform - вездеtransform принимает отрицательные значения, в отличие от leftanimation-fill-mode: both?will-change?*, чтобы ускорить всю страницуtransform: translateZ(0)transition для микровзаимодействий