Как сделать плавную анимацию на css

Оцените материал
(0 голосов)

Проблема

Эластичные переходы и анимация (то есть пружинящие переходы) — это популярный способ придания интерфейсу жизнерадостности и реалистичности, ведь когда объекты движутся в реальной жизни, они редко перемещаются из точки А в точку Б, не демонстрируя никакой упругости. С технической точки зрения пружинящий эффект заключается в том, что после того, как переход достигает конечного значения, он немного отматывается назад, затем снова достигает конечного значения, снова отматывается назад, но уже на меньшее значение, и так повторяется один или несколько раз до тех пор, пока переход окончательно не завершится.

Например, предположим, что мы анимируем элемент, стилизованный под падающий мяч, определяя с помощью  transform переход от  none до  translateY(350px). Разумеется, элемент может демонстрировать пружинящее поведение не только при перемещении по плоскости. Этот эффект способен сделать значительно привлекательнее любой тип перехода, включая:

‰ переходы, затрагивающие размер (например, можно увеличивать элемент при срабатывании  :hover , отображать растущее в размере диалоговое окно, начиная с  transform: scale(0) , анимировать столбики на диаграмме);
‰ переходы, включающие изменение угла (например, вращения или секторные диаграммы, секторы на которых посредством анимации вырастаютс нуля). Несколько библиотек JavaScript содержат встроенные возможности по созданию пружинящей анимации. Однако сегодня мы более не нуждаемся в помощи сценариев для добавления в наши проекты анимации и переходов. Как же тогда наилучшим способом реализовать пружинящий эффект средствами CSS?

 

 


Пружинящая анимация

Нашей первой идеей может быть использование анимации CSS с ключевыми кадрами, как в следующем фрагменте кода:

@keyframes bounce {
60%, 80%, to { transform: translateY(350px); }
70% { transform: translateY(250px); }
90% { transform: translateY(300px); }
}
.ball {
/* Размеры, цвета и т. п. */
animation: bounce 3s;
}
Ключевые кадры в этом фрагменте кода соответствуют шагам, представленным на рисунке. Однако если вы запустите эту анимацию, то заметите, что она выглядит искусственной. Одна из причин, почему так происходит, — каждый раз, когда мячик меняет направление, он продолжает ускоряться, что выглядит очень неестественно. Корень зла здесь в том, что функция расчета времени для этого объекта одна и та же во всех ключевых кадрах. «Функция… чего?» — спросите вы. С каждым переходом и анимацией связана кривая, определяющая, как этот эффект развивается с течением времени (также известная в определенных контекстах под названием сглаживающей кривой (easing curve)). Если вы не указываете функцию расчета времени, то используется функция по умолчанию, а это, в противоположность распространенным ожиданиям, вовсе не линейная функция — она показана на рисунке.

Обратите внимание на момент, обозначенный точкой на графике: когда прошла половина времени, отведенного для эффекта, переход уже завершился на 80%! Функцию расчета времени по умолчанию также можно явно указать с помощью ключевого слова  ease — либо в сокращении  animation / transition , либо в свойстве с полным написанием  animation-timing-function / transition-timing-function . Однако так как  ease — это функция расчета времени по умолчанию, пользы от этого мало. Но существует еще четыре стандартные кривые, с помощью которых вы можете изменить течение анимации; все они показаны на рисунке.


Как вы видите,  ease-out — это противоположность ease-in . Это как раз то, что нам требуется для нашего пружинящего эффекта: мы хотим менять функцию расчета времени на противоположную каждый раз, когда меняется направление движения мячика.

Следовательно, мы можем указать главную функцию расчета времени в свойстве  animation и переопределять ее в ключевых кадрах. Нам нужно, чтобы основному направлению движения соответствовала функция расчета времени с ускорением ( ease-out ), а обратному — с замедлением ( ease-in ):

@keyframes bounce {
60%, 80%, to {
transform: translateY(400px);
animation-timing-function: ease-out;
}
70% { transform: translateY(300px); }
90% { transform: translateY(360px); }
}
.ball {
/* Остальные стили */
animation: bounce 3s ease-in;
}
Если вы протестируете этот код, то заметите, что даже это простое изменение моментально создает гораздо более реалистичный пружинящий эффект. Однако ограничиваться этими пятью стандартными кривыми чрезвычайно печально. Если бы мы могли использовать произвольные функции расчета времени, то достигали бы намного более реалистичных результатов. Например, если пружинящая анимация предназначена для иллюстрации падающего объекта, то более высокое значение ускорения (такое, какое обеспечивает нам  ease) создает более реалистичный эффект. Но как нам
создать противоположность  ease , если ключевого слова для этого не предусмотрено?

Все эти пять кривых задаются посредством (кубических) кривых Безье. Кривые Безье — это кривые, с которыми вы работаете в любых приложениях для создания векторной графики (таких, как Adobe Illustrator). Они определяются как наборы сегментов пути с манипулятором на каждом конце, позволяющим управлять их кривизной (эти манипуляторы часто называют контрольными точками). Сложные кривые содержат огромное количество подобных сегментов, соединенных своими конечными точками.

Функции расчета времени CSS — это кривые Безье с одним только сегментом, поэтому у них только две контрольные точки. В качестве примера на рисунке показана функция расчета времени по умолчанию ( ease ) и соответствующие контрольные точки.


В дополнение к пяти стандартным кривым, которые мы рассмотрели в предыдущем абзаце, существует также функция  cubic-bezier() , позволяющая указывать собственные функции расчета времени. Она принимает четыре аргумента, соответствующих координатам двух контрольных точек для необходимой кривой Безье, в формате  cubic-bezier(x1,y1, x2, y2) , где  (x1, y1) — это координаты первой контрольной точки, а  (x2, y2) — координаты второй контрольной точки. Конечные точки сегмента пути фиксированы: в точке  (0, 0) находится начало перехода (количество прошедшего времени на нуле, прогресс на нуле), а в точке (1, 1) — конец перехода (100% времени прошло, 100% прогресса случилось). Обратите внимание, что наличие одного сегмента с фиксированными конечными точками — не единственное ограничение. Значения координаты  x обеих контрольных точек ограничены диапазоном [0, 1] (то есть мы не можем вынести манипуляторы за пределы графика по горизонтали). Это ограничение появилось не случайно.

Поскольку мы не можем (пока?) путешествовать во времени, невозможно определить переход, начинающийся до того, как он будет запущен, или заканчивающийся после отведенного ему промежутка времени. Но реальное ограничение здесь только одно — это количество узловых точек: возможность определять кривые только с двумя узловыми точками здорово сужает диапазон возможных результатов, но также делает функцию  cubic-bezier() проще в использовании. Несмотря на эти ограничения,  cubic-bezier() позволяет создавать весьма широкий диапазон разнообразных функций расчета времени.

Отсюда логически вытекает, что мы можем перевернуть любую функцию расчета времени, поменяв местами горизонтальные координаты с вертикальными в обеих контрольных точках. Это верно и для ключевых слов; все пять ключевых слов, рассмотренных выше, соответствуют определенным значениям  cubic-bezier() . Например, ease — это эквивалент  cubic-bezier(.25,.1,.25,1) , поэтому противоположной ей будет  cubic-bezier(.1,.25,1,.25) . Результат показан на рисунке.

Таким образом, теперь в нашей пружинящей анимации мы можем использовать  ease, и она будет выглядеть еще реалистичнее:

@keyframes bounce {
60%, 80%, to {
transform: translateY(400px);
animation-timing-function: ease;
}
70% { transform: translateY(300px); }
90% { transform: translateY(360px); }
}
.ball {
/* Стилизация */
animation: bounce 3s cubic-bezier(.1,.25,1,.25);
}
Используя графические инструменты, подобные  http://cubic-bezier.com, мы можем продолжить эксперименты и внести еще больше улучшений в нашу пружинящую анимацию.


ПОПРОБУЙТЕ САМИ!
http://play.csssecrets.io/bounce


Эластичные переходы

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

HTML
<label>
Your username: <input id="username" />
<span class="callout">Only letters, numbers,
underscores (_) and hyphens (-) allowed!</span>
</label>

А CSS-код для переключения стиля отображения может выглядеть как в следующем фрагменте:

input:not(:focus) + .callout {
transform: scale(0);
}
.callout {
transition: .5s transform;
transform-origin: 1.4em -.4em;
}


В текущем варианте реализации, когда пользователь переводит фокус на наше текстовое поле, запускается переход длительностью 0,5 с, работающий, как показано на рисунке.

С ним все прекрасно уже сейчас, но он бы выглядел естественнее и привлекательнее, если бы в конце выноска на мгновение немного раздувалась (то есть увеличивалась до 110% своего размера, а затем снова возвращалась к 100%). Этого можно добиться, преобразовав переход в анимацию
и применив трюк, который мы выучили в предыдущем разделе:

@keyframes elastic-grow {
from { transform: scale(0); }
70% {
transform: scale(1.1);
animation-timing-function:
cubic-bezier(.1,.25,1,.25); /* Обратная к ease */
}
}
input:not(:focus) + .callout { transform: scale(0); }
input:focus + .callout { animation: elastic-grow .5s; }
.callout { transform-origin: 1.4em -.4em; }

СОВЕТ
Если вы использовали для отображения выноски свойство height, а не трансформацию, то заметите, что переход от height: 0 (или любого другого значения) к height:auto не работает, так как auto - это ключевое слово, и оно не может быть выражено в форме анимируемого значения.

В таких случаях следует использовать max-height с достаточно большим значением высоты.

Протестировав это решение, мы убедимся, что оно действительно работает. Результат вы можете видеть на рисунке:

сравните его с предыдущим вариантом перехода. Но, по сути, мы воспользовались анимацией там, где в действительности нам требовался переход. Анимация — очень мощный инструмент, и в такой ситуации, как у нас, когда мы всего лишь хотим добавить переходу немного эластичности, это все равно что забивать гвозди микроскопом или браться за цепную пилу, чтобы отрезать кусочек хлеба. Можно ли добиться чего-то подобного, используя только переход?

Решение опять кроется в настраиваемых функциях расчета времени  cubic-bezier() . Пока мы обсуждали только такие кривые, контрольные точки которых принадлежали диапазону от 0 до 1. Как я уже писал в предыдущем разделе, нельзя выходить за пределы этого диапазона по горизонтали, хотя в будущем это может измениться, если человечество когда-либо изобретет машину времени. Однако по вертикали мы можем выходить за рамки диапазона 0–1, заставляя наш переход демонстрировать менее 0% прогресса или более 100%.

Понимаете, что это означает? Это означает, что при движении от трансформации  scale(0) к трансформации  scale(1) мы можем заставить ее перешагнуть финальное значение, достигнув уровня  scale(1.1) или даже больше, в зависимости от того, насколько крутой мы планируем сделать нашу функцию расчета времени. В нашем случае нужно добавить совсем немного эластичности: мы хотим, чтобы наша функция расчета времени добралась до 110% прогресса (что соответствует  scale(1.1) ), а затем выполнила обратный переход к 100%. Начнем с исходной функции расчета времени  ease ( cubic-bezier(.25,.1,.25,1) ) и передвинем вторую контрольную точку наверх, примерно до уровня bezier(.25,.1,.3,1.5) . Как видно на рисунка, теперь переход достигает 100% приблизительно через 50% отведенного ему времени.

Однако здесь он не останавливается; он продолжает движение вверх, преодолев конечное значение, пока не достигает 110% прогресса приблизительно через 70% времени, а оставшиеся 30% времени занимает возвращение к конечному значению. В результате получается переход, очень схожий с предыдущей анимацией, но для воплощения которого достаточно одной строки кода. Взгляните, как код выглядит теперь:
input:not(:focus) + .callout { transform: scale(0); }
.callout {
transform-origin: 1.4em -.4em;
transition: .5s cubic-bezier(.25,.1,.3,1.5);
}
Однако несмотря на то что, когда мы переводим фокус на текстовое поле, заставляя выноску появляться, наш переход выглядит в точности так, как и ожидалось, в обратной ситуации, когда текстовое поле теряет фокус, а выноска сжимается и исчезает, результат может разочаровывать.

Что же здесь происходит? Как бы странно этот эффект ни выглядел, ничего неожиданного здесь нет: когда мы переводим фокус с нашего поля ввода на другой элемент интерфейса, запускается переход, начальное значение которого равно  scale(1) , а конечное — scale(0) . Следовательно, поскольку применяется та же самая функция расчета времени, переход все так же достигает 110% прогресса через 350 мс. Только на этот раз 110% прогресса соответствует не  scale(1.1) , а  scale(-0.1) ! Но не стоит опускать руки, ведь исправить эту проблему можно с помощью всего лишь одной дополнительной строки кода. Предположим, что для эффекта, когда выноска сжимается, нам требуется обычная функция расчета времени ease . Для того чтобы добавить ее, нужно всего лишь переопределить текущую функцию расчета времени в правиле CSS, определяющем закрытое состояние:
input:not(:focus) + .callout {
transform: scale(0);
transition-timing-function: ease;
}
.callout {
transform-origin: 1.4em -.4em;
transition: .5s cubic-bezier(.25,.1,.3,1.5);
}
Попробуйте выполнить код еще раз, и вы увидите, что выноска закрывается точно так, как это происходило до добавления нашей доработанной функции cubic-bezier(), а отображение выноски сопровождается приятным эластичным эффектом. Самые бдительные читатели наверняка заметили другую проблему: при закрытии выноски создается впечатление, что это происходит слишком медленно. Почему так? Давайте поразмышляем. Когда выноска увеличивается, она достигает 100% конечного размера через 50% времени (то есть через 250 мс). Но когда она уменьшается, движение от 0% до 100% занимает все время, выделенное для перехода (500 мс), поэтому скорость оказывается в два раза меньше. Чтобы исправить этот недостаток, мы можем переопределить также и длительность перехода, используя  transition-duration или же сокращение  transition, которое переопределяет вообще все. Во втором варианте нам не придется явно возвращать функцию расчета времени  ease , потому что это первоначальное значение:
input:not(:focus) + .callout {
transform: scale(0);
transition: .25s;
}
.callout {
transform-origin: 1.4em -.4em;
transition: .5s cubic-bezier(.25,.1,.3,1.5);
}
Хотя эластичность может быть приятным дополнением ко многим типам переходов, с некоторыми она выглядит просто ужасно. Типичная ситуация, когда вы не хотите использовать эластичные переходы, это работа с цветами. Несомненно, эластичные переходы на цветах могут смотреться довольно забавно, но в пользовательских интерфейсах использовать это чаще всего нежелательно. Для того чтобы предотвратить ненамеренное применение эластичных переходов к цветам, попробуйте ограничивать переходы определенными свойствами, вместо того чтобы вообще не указывать никакие, как мы делали выше.

Еслив сокращении  transition мы не указываем никакие свойства, то свойству  transition-property присваивается значение по умолчанию:  all. Это означает, что ко всему, на что может распространяться переход, этот переход будет применен. Следовательно, если позднее к правилу, в котором описываются открывающиеся выноски, мы добавим изменение фона в свойстве  background, то эластичный переход будет применен также и к установке фона. Финальная версия кода выглядит так:
input:not(:focus) + .callout {
transform: scale(0);
transition: .25s transform;
}
.callout {
transform-origin: 1.4em -.4em;
transition: .5s cubic-bezier(.25,.1,.3,1.5)
transform;
}

СОВЕТ
Продолжая тему ограничения переходов конкретными свойствами, вы можете даже ставить в очередь переходы, определенные для разных свойств, используя свойство transition-delay - второе временнóе значение в сокращении transition.

Например, если переход охватывает оба атрибута, width и height, но вы хотите, чтобы сначала изменилась высота и только после этого ширина (эффект, ставший популярным благодаря множеству сценариев для реализации световых коробов), то можете использовать подобное правило: transition: .5s height, .8s .5s width;(то есть задержка перехода для атрибута width равна продолжительности перехода для атрибута height).


ПОПРОБУЙТЕ САМИ!
http://play.csssecrets.io/elastic

 Видеоурок по анимации на css

Десерт на сегодня -сибирская лайка развлекается с кучей листьев

Прочитано 452 раз Последнее изменение Воскресенье, 11 июня 2017 13:44
Другие материалы в этой категории:
Понравилась запись? Подпишитесь на обновления по почте:

Нетология

TemplateMonster

geekbrains.ru/

Поиск по сайту

Google+