Переходы вглубь: timing и кривые

Базовый transition вы уже видели — теперь разберём каждую его часть и научимся управлять характером движения.

transition — это автоматическая плавная интерполяция между двумя состояниями свойства, когда его значение меняется.

В базовом разделе мы писали transition: 0.3s и радовались, что кнопка плавно меняет цвет. Но transition — это на самом деле короткая запись четырёх отдельных свойств, и от их настройки зависит, будет движение выглядеть дёшево или дорого. Профессиональная анимация почти всегда отличается от любительской именно подобранной кривой и таймингом, а не количеством эффектов.

Зачем разбираться глубже

Переход с дефолтной кривой и случайной длительностью выглядит механически. Интерфейс ощущается «живым», когда движение подчиняется простой физике: вещи трогаются с места не мгновенно и тормозят к концу. Управлять этим позволяют четыре подсвойства transition.

Четыре составляющих transition

СвойствоЗа что отвечаетПример
transition-propertyкакое CSS-свойство анимироватьbackground-color, transform, all
transition-durationсколько длится переход0.2s, 300ms
transition-timing-functionхарактер скорости (кривая)ease, linear, cubic-bezier(...)
transition-delayпауза перед стартом0s, 0.1s

Короткая запись складывает их в одну строку именно в этом порядке: property duration timing-function delay.

.button {
  background-color: #3b82f6;
  /* свойство | длительность | кривая | задержка */
  transition: background-color 0.25s ease-in-out 0s;
}
.button:hover {
  background-color: #2563eb;
}

Кривые: ease, linear, cubic-bezier, steps

timing-function описывает, как распределяется скорость во времени. Это самое недооценённое свойство в анимации.

  • linear — постоянная скорость от начала до конца. Подходит для бесконечных вращений и индикаторов загрузки, но для появления элементов выглядит неестественно.
  • ease (значение по умолчанию) — медленный старт, разгон, плавное торможение. Универсальный выбор.
  • ease-in — медленный старт, быстрый конец. Хорош, когда элемент уходит со сцены.
  • ease-out — быстрый старт, плавное торможение. Хорош, когда элемент появляется: глаз сразу видит движение.
  • ease-in-out — плавно с обоих концов, для перемещений «из точки в точку».

Все они — лишь именованные пресеты функции cubic-bezier(x1, y1, x2, y2). Она задаёт кривую Безье двумя контрольными точками; ось X — это время (от 0 до 1), ось Y — прогресс свойства. Когда стандартных кривых мало, рисуют свою:

.card {
  /* «упругий» выход с лёгким перелётом за единицу по Y */
  transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.card:hover {
  transform: translateY(-8px);
}

Значение y2 = 1.56 больше единицы — поэтому элемент на миг «перелетает» цель и возвращается, создавая ощущение пружины. В DevTools кривую можно редактировать мышью прямо на графике.

Дискретные шаги: steps()

Функция steps(n) делает движение не плавным, а скачкообразным — за n равных ступенек. Это классический приём для покадровой спрайт-анимации и эффекта «печатающегося» текста.

.sprite {
  /* 8 кадров за 1 секунду, по одному скачку на кадр */
  transition: background-position 1s steps(8);
}

Что вообще можно анимировать

Браузер умеет интерполировать только те свойства, у которых есть «промежуточные» значения: числа, длины, цвета, проценты, transform, opacity. А вот свойства с дискретными значениями анимировать через transition нельзя.

АнимируетсяНе анимируется (переключается скачком)
opacity, color, background-colordisplay
width, height, padding, marginvisibility (но участвует особым образом)
transform, box-shadow, border-colorfont-family, position

Главная ловушка новичка — попытка плавно «скрыть» блок через display: none. Так не работает: display переключается мгновенно, и перехода opacity вы не увидите. Решают это анимацией opacity вместе с visibility, а сам display при необходимости меняют после завершения.

Переход на нескольких свойствах

Часто нужно анимировать сразу несколько свойств с разными настройками. Их перечисляют через запятую, и каждому можно задать свою длительность и кривую:

.panel {
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity 0.2s ease-out,
    transform 0.35s cubic-bezier(0.22, 1, 0.36, 1) 0.05s;
}
.panel.is-open {
  opacity: 1;
  transform: translateY(0);
}

Здесь прозрачность нарастает быстро (0.2s), а сдвиг идёт дольше и со своей кривой, да ещё с задержкой 0.05s — получается аккуратное «выезжающее» появление. Соблазнительно написать transition: all 0.3s, но all заставляет браузер следить за каждым свойством и легко даёт паразитные анимации (например, мигание тени при перерисовке). Перечисляйте свойства явно.

Как это работает под капотом

Когда значение свойства меняется (по :hover, добавлению класса, инлайн-стилю из JS), браузер сравнивает старое и новое значения. Если для этого свойства задан transition, он не применяет новое значение мгновенно, а на каждом кадре (обычно 60 раз в секунду) вычисляет промежуточное по формуле timing-function(прогресс_времени) и подставляет его. Прогресс времени идёт линейно от 0 до 1, а кривая превращает его в прогресс свойства. Поэтому transition реагирует именно на изменение состояния и не запускается сам по себе при загрузке страницы.

Частые ошибки

  • Указали transition на состоянии :hover, а не на базовом селекторе. Тогда переход плавный «туда», но резкий «обратно». Объявляйте transition на основном правиле элемента.
  • Слишком длинная длительность. 600–800мс на ховер кнопки ощущаются как тормоза. Для микровзаимодействий держитесь диапазона 120–300мс.
  • transition: all повсюду. Бьёт по производительности и даёт неожиданные анимации.
  • Анимация width/height вместо transform. Работает, но дорого — об этом отдельный урок про производительность.

Итоги

  • transition — короткая запись property duration timing-function delay.
  • Характер движения задаёт timing-function; ease-out хорош для появления, linear — для бесконечного вращения.
  • cubic-bezier() рисует любую кривую, steps() — скачкообразное движение для спрайтов.
  • Анимируются только свойства с промежуточными значениями; display — нет.
  • Несколько свойств перечисляйте явно через запятую, избегайте all.
Проверьте себя
1. В каком порядке идут значения в короткой записи transition?
Aduration property delay timing-function
Bproperty duration timing-function delay
Ctiming-function property duration delay
Dproperty timing-function duration delay
2. Какую timing-function логичнее всего выбрать для элемента, который появляется на экране?
Alinear — постоянная скорость
Bease-in — медленный старт, быстрый конец
Cease-out — быстрый старт, плавное торможение
Dsteps(4) — четыре скачка
3. Почему нельзя плавно скрыть блок, анимируя свойство display через transition?
Adisplay можно анимировать только в @keyframes
Bу display нет промежуточных значений, оно переключается скачком
Cdisplay анимируется, но только в Firefox
Dнужно добавить transition-delay больше 1s