Веб-разработка
June 29, 2022

Плавные переходы, Кривые Безье и CSS-анимация — Тренажёр HTML Academy

Наконец, я добрался до анимации в CSS! Для начала вспомним про трансформации, разберёмся как сделать их плавными и затем перейдем уже к анимации по ключевым кадрам!

Кстати, я не прогадал, когда решил пройти сначала Тонкости — в части про переходы мы будем по полной эксплуатировать тему с :checked и ~ селектором. Вообще, появилась у меня мыслишка запилить что-нибудь с использованием этого инструмента. Просто for fun и ради, своего рода, тренировки.

Плавные переходы

Мы немного затрагивали плавные переходы, когда разбирали какие бывают трансформации, но по умолчанию, все они происходят мгновенно. Точнее трансформацией transform мы лишь задавали разные свойства состояния — у элемента их может быть несколько, например, базовое и :hover.

Переход же между этими состояниями мгновенен и за плавность или, как написали в тренажёре, «нежность» перехода из одного состояния элемента в другой отвечает семейство свойств transitions.

Свойства перехода

Первое и самое важное свойство, без которого вообще не работает плавность переходов — свойство длительности перехода transition-duration. Оно принимает числовое значение в секундах s или миллисекундах ms:

transition-duration: 0.3s; 
transition-duration: 300ms; 

По умолчанию, свойства семейства transitions принимают во внимание все свойства, которые можно плавно изменить или анимировать — это размерные, цветовые и позиционные свойства. Чтобы контролировать какой конкретно параметр нужно изменять, используется свойство transition-property.

По умолчанию имеет значение all, то бишь все, в иных случаях принимает буквально названия CSS-свойств на которые будет распространяться плавный переход и его параметры. Можно задавать сразу несколько параметров через запятую — в этом плане работает схема из background, значения сопоставляются по их позиции:

transition-property: width, height;
transition-duration: 1s, 5s; /* ширина меняется за 1s, высота за 5s */

Следующее полезное свойство — задержка перехода transition-delay. Как и длительность принимает значение секунд s и миллисекунд ms. Критично необходимое свойство при создании плавной анимации интерфейсов, хотя для новичков может показаться иначе.

Функция перехода

Последнее свойство супер полезное, но и достаточно сложное для понимания — это свойство функции перехода transition-timing-function. Все переходы помимо длительности имеют, можно сказать, «скорость» перехода — она может быть постоянной или «линейной», а может быть более сложной, например, замедляться в начале или в конце.

Отношение времени к изменению состояния можно, условно, записать в функцию, а построив график этой функции можно наглядно посмотреть как будет вести себя элемент. Именно эту функцию принимает за значение свойство transition-timing-function.

Но не стоит пугаться математики! Во-первых, математика проще чем думает большинство, но об этом как-нибудь в другой раз. Во-вторых, для нашего свойства есть заготовки функций в виде ключевых слов:

  • ease — значение по умолчанию для всех переходов, немного замедляется в начале, резкий переход в середине и плавное замедление к концу;
  • linear — линейный, равномерный переход, без ускорений и замедлений;
  • ease-in — слегка замедляется на старте;
  • ease-out — слегка замедляется в конце;
  • ease-in-out — похож на ease, но имеет более резкий переход в середине.

На самом деле все эти функции являются упрощёнными образами функций кубических Кривых Безье. Свойство transition-timing-function может принимать значение в виде самой функции cubic-bezier(x1, y1, x2, y2), где аргументами являются координаты опорных точек.

Что за опорные точки? Те кто хотя бы раз в жизни работал с векторной графикой в каком-нибудь Adobe Illustrator уже знают про кривые Безье, хотя могут об этом и не догадываться. Зато опорные точки они точно видели.

Это из Иллюстратора

Итак, разбираемся. Обычный отрезок состоит из двух точек — начало и конец отрезка. У этих точек могут быть свои координаты, но для упрощения сделаем начало в нуле 0, а конец в единице по обеим осям 1, 1.

Получается прямая линия, как на графике с линейной linear функцией (см. выше), но нас больше интересуют кривые. Как нам их деформировать? Нужно добавить больше точек! Точнее две — по одной для начала и для конца.

Соединив эти опорные точки с началом и концом мы получим ломанную кривую, очень отдалённо похожую на то, что нам надо:

Плавной её делает уже «магия» математики, вдаваться в её подробности не будем, но всю эту магию несёт в себе сама функция cubic-bezier(). В любимой Википедии отлично показывается как строится плавная кривая из ломанной, если кому-то интересно.

Наша функция cubic-bezier() в аргументы принимает координаты опорной точки начала — x1 по горизонтали, y1 по вертикали, и аналогично опорной точки конца — x2 и y2.

Координаты могут совпадать, как друг с другом, так и с крайними точками. Например, линейная linear, формально, тоже имеет опорные точки, просто их координаты совпадают с началом и концом:

cubic-bezier(0, 0, 1, 1)

У ease-in и ease-out совпадают координаты одной из опорных точек:

cubic-bezier(0.42, 0, 1, 1) /* ease-in */
cubic-bezier(0, 0, 0.58, 1) /* ease-out */

Естественно, на эту тему есть уже много сервисов, одно из них рекомендует Академия — https://cubic-bezier.com/ — в нём можно поиграться с опорными точками и построить ту функцию, которая вам нужна. Другой вопрос, что в 90% случаях хватает ease и её производных, но для оставшихся 10% пригодиться!

На последок — у свойства transition-timing-function есть еще одно значение! Это тоже функция — steps(), она преобразует переход в пошаговый. Она принимает два аргумента, первый — количество шагов, а второй — одно из двух ключевых слов start или end.

При заданном start первый шаг выполняется одновременно с началом перехода, а в случае c end последний шаг будет выполнен вместе с завершением перехода. Грубо говоря, указывается под что подстроится функция.

Мини-мастерская переходов

Больше половины части занимают именно примеры, так как свойств на самом деле не много. Я сначала хотел их пропустить, но решил, что хотя бы упомяну, так как сделано реально интересно и делаем прикольные штуки.

Первым делом мы сделали самое очевидное — кнопочки. Смена происходит через добавление класса с помощью JS. Добавили еще эффект расширения тени, который, на мой взгляд, сделан хреновенько:

Следом сделали красивые чекбоксы. Здесь и далее уже используется :checked и селектор ~ для стилизации псевдоэлементов выбранных пунктов. Сама идея с галочкой из поля прикольная, но явно требует доработать момент возвращения галочки в состояние пустого квадрата, а то стороны начинают появляться раньше чем надо:

Далее, конечно же, потренировались на радиокнопках, тут всё ещё проще — два псевдоэлемента, один перекрывает другой при выделении, анимирована scale() трансформация:

Следом сделали переключатели — их достаточно большое количество в интерфейсах. Здесь мы сделали их через чекбоксы — один псевдоэлемент это линия, а второй анимируемый ползунок, простой translate со сменой цвета:

Следующим мы сделали так называемый «бургер» и точка меню, обычно такие делают в мобильных приложениях или в мобильных версиях сайта. Честно скажу, я до этого делал их тупо через смену картинок, не приходило в голову делать через элементы и псевдоэлементы. А подобные анимации, я думал, делаются исключительно через JS, но нет!

И на последок поигрались с полями формы. Поле формы тоже одно из самых «труднодоступных» для стилизации элементов. Здесь мы используем <label> как placeholder и псевдоэлементы как подчеркивания:

CSS Анимация

Мы можем управлять стилями некоторых состояний одного элемента, например, выбранным :checked, наведённым :hover, ну и базовым. Выше мы разобрали как сделать плавный переход между этими заготовленными состояниями, то есть между двумя стилями, с помощью семейства свойств transition.

И хотя сами по себе переходы очень крутой инструмент и решает, наверное, большую часть задач анимации в интерфейсах, всё же мы можем пойти дальше — с помощью CSS создавать сложные анимации с большим количеством состояний одного элемента и переходов между ними. За это отвечает семейство свойств animation.

Сама анимация в CSS состоит из двух частей — функция самой анимации или, как её назвали в тренажёре, набор ключевых кадров @keyframes и самих свойств семейства animation, которые применяются к элементам.

Ключевые кадры

Сначала разберёмся с ключевыми кадрами @keyframes — если сильно упростить описание этой конструкции, то с её помощью мы задаём состояния элемента — ключевые кадры, и стили этих состояний в нужный момент анимации:

@keyframes stretching { /* название функции-анимации */
  0% { /* состояние в начале */
    width: 100px;
  }
  100% { /* состояние в конце */
    width: 200px;
  }
}

Конструкция похожа, на мой взгляд, на объявление какой-либо функции из любого C-подобного языка, что недалеко от правды. Мы объявляем, что функция stretching — это анимация, которая изменит элемент от состояния ключевого кадра 0%, до состояния ключевого кадра 100%.

Имя анимации можно задавать любое, главное помнить, что оно чувствительно к регистру. Ключевые кадры обозначаются либо процентами, либо есть два ключевых слова — from, что по сути равно 0% и to, что равно 100%.

@keyframes square-move {
  from   {top: 0px; left: 0px; background: red;}
  25%  {top: 0px; left: 100px; background: blue;}
  50%  {top: 100px; left: 100px; background: yellow;}
  75%  {top: 100px; left: 0px; background: green;}
  to {top: 0px; left: 0px; background: red;}
}

Кадры можно группировать, если какое-то состояние одинаково для двух моментов анимации. Это используется в разных целях, например, анимацию square-move можно немного сократить:

@keyframes square-move {
  from, to   {top: 0px; left: 0px; background: red; } /* или 0%, 100% */
  25%  {top: 0px; left: 100px; background: blue;}
  50%  {top: 100px; left: 100px; background: yellow;}
  75%  {top: 100px; left: 0px; background: green;}
}

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

@keyframes square-move-stop {
  from, to   {top: 0px; left: 0px; background: red;}
  40%, 60%  {top: 0px; left: 100px; background: blue;}
  /* между 40% и 60% нет ключевых кадров - их состояние одинаково */
}

Итак, создали мы крутой набор ключевых кадров, что с ним дальше делать?

Свойства анимации

Переходим к свойствам семейства animation! Они во многом очень похожи на свойства transition с небольшими дополнениями.

Первое свойство, собственно, применяет набор ключевых кадров к элементу — это свойство animation-name. Оно принимает имя заготовленного набора ключевых кадров или, иными словами, вызывает функцию анимации.

Второе свойство, без которого анимация работать не будет, это свойство длительности анимации animation-duration. Как и его брат-близнец из переходов transition-duration, принимает числовое значение в секундах s или миллисекундах ms.

Без этих двух свойств анимация элементарно не применится, они обязательны. Одному элементу можно задать сразу множество наборов ключевых кадров, они указываются в animation-name через запятую, а все остальные параметры в свойствах, например тот же animation-duration, сопоставляются по порядку:

.element {
  animation-name: move, stretch; 
  animation-duration: 2s, 6s; /* 2s длится move, 6s stretch */
}

Еще два свойства-родственника из переходов transition — это свойство задержки анимации animation-delay и свойство задающее функцию перехода, но уже для всей анимации animation-timing-function.

Про задержку, я думаю, и так понятно, с ней, кстати, тоже работают правила множественных значений. Функция перехода тоже работает похожим образом — такие же значения, типа ease, linear и т. д., и тоже можно применять функции cubic-bezier() и steps().

Немного, возможно, не очевидный нюанс — функция перехода заданная в animation-timing-function применяется ко всем переходам между ключевыми кадрами, а не к прогрессу анимации в целом. То есть каждая анимация между ключевыми кадрами будет по умолчанию ease.

Переходим к уникальным для анимации свойствам. Первое такое — свойство количества итераций или проигрывания анимации animation-iteration-count. Принимает либо число итераций, либо ключевое слово infinite, обозначающее бесконечное проигрывание анимации.

Свойство задержки animation-delay, кстати, применяется только один раз до начала всей анимации, то есть при повторении никакой задержки не будет. Нужно это иметь ввиду, если задержка критична для каждой итерации.

Следующее свойство отвечает за направление анимации animation-direction. По умолчанию оно «прямое», то есть анимация выполняется от 0% до 100% и свойство имеет значение normal. Другие же значения:

  • reverse — «разворачивает» анимацию, она начинает выполняться в обратную сторону — со 100% до 0%, при этом функция перехода НЕ разворачивается.
  • alternate — разворачивает каждую чётную итерацию, таким образом помогает зациклить анимацию без «разрывов» в состояниях элемента.
  • alternate-reverse — разворачивает каждую нечётную итерацию.

По умолчанию, после завершения анимации элемент возвращается в своё исходное состояние. Этим, естественно, можно управлять и с помощью комбинаций вышеописанных свойств, но конкретно за это отвечает свойство animation-fill-mode, которое явно указывает на состояние элемента после выполнения анимации, пусть даже с одной итерацией.

Имеет всего четыре значения, включая значение по умолчанию:

  • none — по умолчанию, после завершения анимации элемент вернётся в исходное состояние, которое может отличаться от ключевого кадра 0% или from — это важно, это не равнозначные состояния.
  • frowards — после завершения анимации элемент останется в состоянии последнего момента анимации — это особенно важно при каком-либо значении свойства animation-duration, т. к. последним состоянием может быть и 0%, если задан reverse или alternate.
  • backwards — наоборот, после завершения анимации элемент примет состояние начального кадра анимации, который, опять же, может быть не 0% или from, в зависимости от направления анимации. Это значение имеет еще один эффект — элемент даже до начала анимации принимает состояние указанное в кадре 0%, даже если у анимации есть задержка.
  • both — комбинирует два значения, до начала анимации элемент принимает состояние первого ключевого кадра, а после завершения анимации элемент останется в состоянии последнего момента анимации.

Последнее свойство — это управление паузой или состоянием проигрывания анимации animation-play-state. Имеет всего два значение — running по умолчанию и paused, то бишь — поставить анимацию на паузу. Понятное дело, это свойство-триггер, как например, display: none;.


На этом с анимацией всё! Более того, на этом мы, формально, заканчиваем изучение HTML/CSS в тренажёрах! Далее у нас Less, который, конечно, препроцессор CSS, но всё-таки уже не совсем про стили, а скорее про оптимизацию работы с CSS. Потом SVG и после добьем JavaScript.

Поздравляю себя! :D 

На самом деле я в какой-то момент закопался так, что уже не думал когда закончу, а оно пришло неожиданно. Я уверен, я еще помучаюсь на JavaScript, но в остальном конец уже более-менее ощутим. По крайней мере с вёрсткой.

Спасибо вам за ваше внимание! Продуктивной вам работы!

Ссылки на мои социальные сети, там анонсы постов, некоторые мои короткие записи, мыслишки и всякое на отвлечённые темы:

Telegram — Twitter — Instagram — Facebook — VK

Заходите куда удобно вам и подписывайтесь! Еще раз спасибо за внимание!