CSS cubic-bezier(): как на самом деле работают кривые плавности
Что означают четыре числа в cubic-bezier(), как построить эффекты проскока и «замаха» и когда достаточно встроенных ключевых слов ease.
ease-in-out закрывает больше случаев, чем ему обычно приписывают. Но время от времени дизайнер присылает Lottie-анимацию с отскоком, а менеджер просит, чтобы модальное окно «выпрыгивало». Тут пяти встроенных ключевых слов уже не хватает — и в дело идёт cubic-bezier(). Математика за ним проще, чем кажется по названию.
В Генераторе кривых cubic-bezier можно перетаскивать две точки на графике и сразу видеть, как шарик движется по получившейся кривой. В этой статье — что эти точки на самом деле делают.
Две точки, четыре числа
cubic-bezier(x1, y1, x2, y2) описывает кривую между двумя фиксированными концами: она всегда начинается в точке (0, 0) и заканчивается в (1, 1). Этих двух точек нет среди аргументов функции, потому что они не могут сдвинуться: анимация всегда стартует с 0% прогресса и заканчивается на 100%. Четыре числа, которые вы задаёте, — это координаты двух контрольных точек, которые тянут кривую между этими концами.
Оси удобно читать как время и прогресс. X — прошедшее время анимации, от 0 до 1. Y — насколько далеко продвинулось анимируемое свойство, тоже в масштабе от 0 до 1, только y на самом деле этим диапазоном не ограничен. Именно отсюда берутся интересные эффекты.
Почему x ограничен, а y нет
x не может быть меньше 0 или больше 1, потому что время в рамках одного прогона анимации не бывает отрицательным и не выходит за собственный конец. Любое значение x браузер обрежет до этого диапазона.
У y такого ограничения нет. Ничто не мешает кривой в середине анимации поднять прогресс свойства до 120%, а затем вернуть его к 100%. Это не баг в математике — это осознанная возможность, и именно на ней строятся эффекты отскока и проскока.
Как построить проскок
Потяните вторую контрольную точку (ту, что ближе к концу кривой) выше y = 1. Что-то вроде cubic-bezier(0.34, 1.56, 0.64, 1) отправит анимируемый элемент за целевое положение, прежде чем он туда вернётся, — получается мягкий, пружинистый отскок. Я часто использую это на всплывающих уведомлениях и галочках успешного действия: обычный ease-out на их фоне ощущается плоско, а версия с проскоком ничего не стоит по весу — это всё равно одно CSS-значение.
Тот же приём работает и в начале: потяните первую контрольную точку ниже y = 0, и элемент чуть отклонится назад перед тем, как двинуться вперёд, — эффект «замаха». Аниматоры используют этот приём десятилетиями в рисованной анимации, просто здесь он выражен двумя числами вместо стопки нарисованных вручную кадров.
Пять ключевых слов, разложенных на числа
Каждое именованное ключевое слово плавности — это сокращение для конкретного вызова cubic-bezier():
linear— этоcubic-bezier(0, 0, 1, 1): постоянная скорость, без ускоренияease— этоcubic-bezier(0.25, 0.1, 0.25, 1): значение по умолчанию в CSS, быстрый старт и плавная остановкаease-in— этоcubic-bezier(0.42, 0, 1, 1): медленный старт, без плавности в концеease-out— этоcubic-bezier(0, 0, 0.58, 1): без плавности в начале, медленное завершениеease-in-out— этоcubic-bezier(0.42, 0, 0.58, 1): симметричные разгон и торможение
ease — это то, что получится, если написать transition вообще без timing-функции, и, честно говоря, это правильный выбор чаще, чем принято думать. За свою кривую стоит браться, когда стандартная действительно не подходит, а не по умолчанию из привычки.
Где кривая на самом деле применяется
cubic-bezier() работает везде, где CSS ожидает timing-функцию: в transition-timing-function, в animation-timing-function и в timing-функциях отдельных кадров внутри @keyframes. Модальному окну, которому нужно открываться резче, чем закрываться, не нужен JavaScript — достаточно задать одну кривую в transition-timing-function для открытия и более спокойную для закрытия.
Поиграйте с графиком в Генераторе кривых cubic-bezier, нажмите «Играть», чтобы увидеть движение вживую, и скопируйте значение, когда оно понравится.