Застосування анімацій CSS

Анімації CSS роблять можливим анімування переходів від однієї конфігурації стилю CSS до іншої. Анімації складаються з двох деталей: стилю, що описує анімацію CSS, та набору ключових кадрів, котрі вказують на початковий і кінцевий стани стилю анімації, а також можливі проміжні точки.

Є три ключові переваги анімацій CSS над традиційними методиками анімацій, що працюють на сценаріях:

  1. Їх легко застосовувати для простих анімацій; їх можна створювати навіть без знання JavaScript.
  2. Такі анімації добре працюють навіть тоді, коли система перебуває під навантаженням середнього рівня. Прості анімації на JavaScript нерідко можуть мати погану швидкодію. Рушій візуалізації може застосовувати пропускання кадрів чи інші методики, аби підтримувати швидкодію настільки плавною, наскільки можливо.
  3. Передача браузерові контролю над послідовністю анімації дозволяє йому оптимізувати швидкодію й ефективність шляхом, наприклад, зниження частоти оновлення анімації у вкладках, котрі наразі приховані.

Налаштування анімації

Щоб створити послідовність анімації CSS, елемент, що анімується, оздоблюють властивістю animation чи її підвластивостями. Це дає змогу налаштовувати хронометраж, тривалість та інші подробиці того, як повинна перебігати анімація. Це не налаштовує фактичний вигляд анімації, він виконується за допомогою директиви @keyframes як це описано в розділі Визначення послідовності анімації за допомогою ключових кадрів нижче.

Підвластивості animation – такі:

animation-composition

Задає складену операцію для використання в тих випадках, коли на одну властивість одночасно впливають кілька анімацій. Ця властивість не є частиною властивості-скорочення animation.

animation-delay

Задає затримку між завантаженням елемента і стартом послідовності анімації, а також те, чи повинна анімація початися зразу зі свого старту, чи якоїсь позиції посередині.

animation-direction

Задає те, чи матиме перша ітерація прямий хід, чи зворотний, і чи будуть наступні ітерації мати почергову зміну ходу або будуть скидатися на початкову точку й повторюватися.

animation-duration

Задає тривалість, протягом якої анімація завершує один цикл.

animation-fill-mode

Задає те, як анімація застосовує стилі до своєї цілі, до і після її виконання.

[!NOTE] У разі анімації в режимі заповнення вперед – анімовані властивості поводяться так, ніби включені в значення властивості will-change. Якщо під час анімації було створено новий контекст нагромадження, то цільовий елемент зберігає цей контекст після завершення анімації.

animation-iteration-count

Задає те, скільки разів повинна повторитися анімація.

animation-name

Задає ім'я директиви @keyframes, котра описує ключові кадри анімації.

animation-play-state

Задає паузу чи відновлення анімації.

animation-timeline

Задає часову шкалу, що використовується для контролю прогресу анімації CSS.

animation-timing-function

Задає те, як анімація переходить між ключовими кадрами, шляхом встановлення кривих прискорення.

Визначення послідовності анімації за допомогою ключових кадрів

Після налаштування хронометражу анімації, треба визначити її вигляд. Це виконується шляхом створення одного чи більше ключових кадрів за допомогою директиви @keyframes. Кожний ключовий кадр описує те, як повинен візуалізуватися анімований елемент у певну мить протягом послідовності анімації.

Оскільки хронометраж анімації визначається стилем CSS, котрий налаштовує анімацію, ключові кадри використовують відсотки для вказівки на час, коли під час анімації вони спрацюють. 0% вказує на першу мить послідовності анімації, а 100% – на фінальний стан анімації. Через те, що ці дві миті такі важливі, вони мають особливі псевдоніми: from і to. Обидва – необов'язкові. Якщо from/0% чи to/100% – не задано, то браузер почне або завершить анімацію, використовуючи обчислені значення всіх атрибутів.

Можна, хоча не обов'язково, включити додаткові ключові кадри, котрі описують проміжні кроки між початком і кінцем анімації.

Застосування скорочення animation

Скорочення animation – корисне для економії простору. Як приклад, частина правил, що використовуються в цій статті:

p {
  animation-duration: 3s;
  animation-name: slide-in;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}

...можуть бути замінені шляхом використання скорочення animation.

p {
  animation: 3s infinite alternate slide-in;
}

Аби дізнатися більше про послідовність, у котрій за допомогою скорочення animation можуть задаватися різні значення властивостей анімації, зверніться до довідкової сторінки animation.

Задання декількох значень властивостей анімації

Специфічні властивості анімації CSS можуть приймати декілька значень, розділених комами. Ця можливість може використовуватися, коли треба застосувати до одного правила декілька анімацій, встановивши для кожної з них різні тривалості, кількості ітерацій тощо. Погляньмо на кілька невеличких прикладів, котрі пояснюють різні перестановки.

У цьому першому прикладі є три тривалості й три кількості ітерацій. Тож кожна анімація отримує значення тривалості й кількості ітерацій на такій само позиції, що й ім'я анімації. Анімація fadeInOut отримує тривалість 2.5s й кількість ітерацій 2, а анімація bounce отримує тривалість 1s і кількість ітерацій 5.

animation-name: fadeInOut, moveLeft300px, bounce;
animation-duration: 2.5s, 5s, 1s;
animation-iteration-count: 2, 1, 5;

У цьому, другому прикладі, задаються три імені анімацій, але є лише одна тривалість й одна кількість ітерацій. У такому випадку всі три анімації отримують однакові тривалість і кількість анімацій.

animation-name: fadeInOut, moveLeft300px, bounce;
animation-duration: 3s;
animation-iteration-count: 1;

У цьому, третьому прикладі, задаються три анімації, але лише дві тривалості й дві кількості ітерацій. У таких випадках, коли в списку недостатньо значень, аби надати кожній анімації окреме, присвоєння значень йде від першого елемента до останнього в доступному списку, а потім продовжується від першого. Таким чином, fadeInOut отримує тривалість 2.5s, moveLeft300px отримує тривалість 5s, що є останнім значенням в списку значень тривалості. Присвоєння значень тривалості далі перестрибує на перше значення; bounce, таким чином, отримує тривалість 2.5s. Значення кількості ітерацій (та інші задані значення властивостей) присвоюються так само.

animation-name: fadeInOut, moveLeft300px, bounce;
animation-duration: 2.5s, 5s;
animation-iteration-count: 2, 1;

Якщо розбіжність числа анімацій та числа значень властивостей анімації – зворотна, скажімо, є п'ять значень animation-duration для трьох значень animation-name, то надлишкові чи невикористані значення властивості, в цьому випадку – два значення animation-duration, не застосовуються до жодної анімації й ігноруються.

Приклади

[!NOTE] Частина старих браузерів (до 2017 року) могли потребувати префіксів; живі приклади, котрі для перегляду треба клацнути, включають синтаксис із префіксом -webkit.

Текст, що ковзає вікном браузера

Цей базовий приклад оздоблює елемент <p> за допомогою властивостей переходу translate і scale, щоб текст заїжджав з-за правого краю вікна браузера.

p {
  animation-duration: 3s;
  animation-name: slide-in;
}

@keyframes slide-in {
  from {
    translate: 150vw 0;
    scale: 200% 1;
  }

  to {
    translate: 0 0;
    scale: 100% 1;
  }
}

В цьому прикладі стиль елемента <p> задає, що виконання анімації від її початку до завершення повинно зайняти 3 секунди, за допомогою властивості animation-duration, і що ім'я директиви @keyframes, котра визначає ключові фрейми для послідовності анімації, – slide-in.

У цьому випадку є лише два ключові кадри. Перший відбувається на 0% (за допомогою псевдоніма from). Тут ми налаштовуємо властивість translate елемента на 150vw (тобто за межами правого краю елемента-контейнера) і властивість scale елемента на 200% (або вдвічі більше його типового розміру), через що абзац стає удвічі ширшим за свій контейнерний блок <body>. Це призводить до того, що перший кадр анімації має заголовок, нанесений за правим краєм вікна браузера.

Другий ключовий кадр відбувається на 100% (за допомогою псевдоніма to). Властивість translate задана зі значенням 0%, а властивість scale – зі значенням 1, тобто 100%. Це змушує заголовок завершити анімацію в своєму усталеному стані, прилягаючи до лівого краю області вмісту.

<p>
  Коли лежиш в полі лицем до неба і вслухаєшся в многоголосу тишу полів, то
  помічаєш, що в ній щось є не земне, а небесне.
</p>

[!NOTE] Аби побачити анімацію – перезавантажте сторінку.

Додавання іще однієї анімації ключових кадрів

Додаймо до анімації попереднього прикладу ще один ключовий кадр. Скажімо, що хочемо, аби дієслово "вслухаєшся" ставало рожевим, збільшувалося, а потім знову зменшувалося до свого початкового розміру й кольору, поки рухається з правого краю на лівий. Попри те, що можна було б змінити властивість font-size, зміна будь-яких властивостей, що впливають на рамкову модель, негативно впливає на продуктивність. Замість цього обгорнімо це слово в <span>, а тоді масштабуймо та призначмо колір окремо. Це потребує додавання другої анімації, що впливає лише на <span>:

@keyframes grow-shrink {
  25%,
  75% {
    scale: 100%;
  }
  50% {
    scale: 200%;
    color: magenta;
  }
}

Повний код тепер має наступний вигляд:

p {
  animation-duration: 3s;
  animation-name: slide-in;
}

p span {
  display: inline-block;
  animation-duration: 3s;
  animation-name: grow-shrink;
}

@keyframes slide-in {
  from {
    translate: 150vw 0;
    scale: 200% 1;
  }
  to {
    translate: 0 0;
    scale: 100% 1;
  }
}

@keyframes grow-shrink {
  25%,
  75% {
    scale: 100%;
  }

  50% {
    scale: 200%;
    color: magenta;
  }
}

Додано <span> навколо "вслухаєшся":

<p>
  Коли лежиш в полі лицем до неба і <span>вслухаєшся</span> в многоголосу тишу
  полів, то помічаєш, що в ній щось є не земне, а небесне.
</p>

Такий код каже браузерові, що на 75% послідовності анімації заголовок повинен мати лівий зовнішній відступ 25%, а ширину – 150%.

Це каже браузерові, що це слово повинно бути звичайним для перших і останніх 25% анімації, але ставати рожевим під час збільшення та зменшення посередині. Властивість display для <span> задана з inline-block, оскільки властивості transform не впливають на незаміщений вміст рядного рівня.

[!NOTE] Аби побачити анімацію – перезавантажте сторінку.

Повторення анімації

Щоб анімація повторювалася, треба додати властивість animation-iteration-count, котра вказує на те, скільки разів треба повторити анімацію. У цьому випадку використаймо infinite, аби анімація повторювалася нескінченно:

p {
  animation-duration: 3s;
  animation-name: slide-in;
  animation-iteration-count: infinite;
}

Анімація з рухом назад і вперед

Приклад вище змусив анімацію повторюватися, але вельми дивно, що вона щоразу скаче на початок, коли починається. Насправді хочеться, аби текст рухався текстом назад і вперед. Це легко реалізувати, задавши властивість animation-direction зі значенням alternate:

p {
  animation-duration: 3s;
  animation-name: slide-in;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}

Використання подій анімації

Над анімаціями можна отримати додатковий контроль – а також корисну інформацію про них – шляхом використання подій анімації. Такі події, представлені об'єктом AnimationEvent, можуть використовуватися для відстеження того, коли анімації починаються, завершуються і коли починають нову ітерацію. Кожна подія містить час, коли вона відбулася, а також ім'я анімації, що спричинила подію.

Змінімо приклад з ковзанням тексту, аби вивести певну інформацію про кожну подію анімації, що спрацьовує, аби побачити, як ці події працюють.

Додана та сама анімація ключових кадрів, що і в попередньому прикладі. Ця анімація триватиме 3 секунди, зватиметься "slide-in", повторюватиметься 3 рази, і кожного разу буде рухатися в протилежному напрямку. У @keyframes масштаб і переклад маніпулюються вздовж осі абсцис, щоб змусити елемент ковзати по екрану.

.slide-in {
  animation-duration: 3s;
  animation-name: slide-in;
  animation-iteration-count: 3;
  animation-direction: alternate;
}

Додавання слухачів подій анімації

Застосуймо код JavaScript для слухання всіх трьох можливих подій анімації. Код нижче налаштовує слухачів подій; він викликається, коли документ завантажився, щоб усе приготувати.

const element = document.getElementById("watch-me");
element.addEventListener("animationstart", listener, false);
element.addEventListener("animationend", listener, false);
element.addEventListener("animationiteration", listener, false);

element.className = "slide-in";

Це вельми стандартний код; подробиці про те, як він працює, можна знайти в документації eventTarget.addEventListener(). Останнє, що робить цей код – присвоєння class елемента, що анімується, значення "slide-in"; це робиться, щоб почати анімацію.

Чому? Бо подія animationstart спрацьовує, щойно почалась анімація, а в нашому випадку це відбувається до спрацювання нашого коду. Тож почнімо анімацію самі, встановивши клас елемента зі стилем, що, зрештою, анімується.

Отримання подій

Події передаються функції listener(), показаній нижче.

function listener(event) {
  const l = document.createElement("li");
  switch (event.type) {
    case "animationstart":
      l.textContent = `Почалося: час, що минув – ${event.elapsedTime}`;
      break;
    case "animationend":
      l.textContent = `Скінчилося: час, що минув – ${event.elapsedTime}`;
      break;
    case "animationiteration":
      l.textContent = `Новий цикл почався після ${event.elapsedTime}`;
      break;
  }
  document.getElementById("output").appendChild(l);
}

Цей код також є вельми простим. Він дивиться на event.type, аби визначити, якого роду подія анімації сталася, а потім додає відповідний запис у <ul> (невпорядкований список), що використовується як журнал таких подій.

Вивід після всього цього має якийсь такий вигляд:

  • Почалося: час, що минув – 0
  • Новий цикл почався після 3.01200008392334
  • Новий цикл почався після 6.00600004196167
  • Скінчилося: час, що минув – 9.234000205993652

Зверніть увагу, що миті часу дуже наближені до очікуваних згідно з заданим часом анімації, але не рівні їм. Також зверніть увагу, що після фінальної ітерації анімації подія animationiteration не надсилається; замість неї надсилається подія animationend.

Заради повноти – ось HTML, що виводить вміст сторінки, включно зі списком, до якого сценарій вставляє інформацію про отримані події:

<h1 id="watch-me">Дивіться, як я рухаюсь</h1>
<p>
  Цей приклад демонструє, як використовувати анімації CSS, аби елементи
  <code>H1</code> рухались по сторінці.
</p>
<p>
  На додачу – щоразу, коли спрацьовує подія анімації, виводиться певний текст,
  тож ці події помітні в дії.
</p>
<ul id="output"></ul>

І ось – справжній вивід.

[!NOTE] Аби побачити анімацію – перезавантажте сторінку.

Анімування display та content-visibility

Цей приклад демонструє те, як можна анімувати властивості display та content-visibility. Це корисно для створення анімацій входу-виходу, де, наприклад, треба вилучити контейнер із DOM за допомогою display: none, але зробити його зникнення плавним за допомогою opacity, а не миттєвим.

Браузери, що це підтримують, анімують display та content-visibility за допомогою варіації на дискретному типі анімації. Це, як правило, означає, що властивість перемикається між двома значеннями на 50% шляху анімації між ними.

Проте є виняток, а саме – коли анімується display: none чи content-visibility: hidden до значення видимості. У цьому випадку браузер перемикається між двома значеннями так, щоб анімований вміст був видимим протягом усієї тривалості анімації.

Отже, наприклад:

  • Коли display анімується від none до block (чи іншого видимого значення display), значення перемикається на block на 0% тривалості анімації, щоб воно було видимим протягом усієї анімації.
  • Коли display анімується від block (чи іншого видимого значення display) до none, значення перемикається на none на 100% тривалості анімації, щоб воно було видимим протягом усієї анімації.

HTML

HTML тут містить два елементи <p> з <div> між ними, котрий ми анімуємо від display none до block.

<p>
  Клацніть десь на екрані чи натисніть будь-яку клавішу, щоб перемкнути
  <code>&lt;div&gt;</code> між прихованістю та показом.
</p>

<div>
  Це елемент <code>&lt;div&gt;</code>, що анімується між
  <code>display: none; opacity: 0</code> та
  <code>display: block; opacity: 1</code>. Цікаво, чи не так?
</div>

<p>
  Це інший абзац, потрібний для того, щоб показати, що
  <code>display: none;</code> застосовується й вилучається до вищезазначеного
  <code>&lt;div&gt; </code>. Якби змінювалася лише його <code>opacity</code>,
  він завжди займав би місце в DOM.
</p>

CSS

html {
  height: 100vh;
}

div {
  font-size: 1.6rem;
  padding: 20px;
  border: 3px solid red;
  border-radius: 20px;
  width: 480px;
  opacity: 0;
  display: none;
}

/* Класи анімації */

div.fade-in {
  display: block;
  animation: fade-in 0.7s ease-in forwards;
}

div.fade-out {
  animation: fade-out 0.7s ease-out forwards;
}

/* Ключові кадри анімації */

@keyframes fade-in {
  0% {
    opacity: 0;
    display: none;
  }

  100% {
    opacity: 1;
    display: block;
  }
}

@keyframes fade-out {
  0% {
    opacity: 1;
    display: block;
  }

  100% {
    opacity: 0;
    display: none;
  }
}

Зверніть увагу на присутність в анімаціях ключових кадрів властивості display.

JavaScript

Врешті-решт, додаймо трохи JavaScript, щоб налаштувати слухачі подій, які запускатимуть анімації. А саме – додаймо <div> клас fade-in, коли хочемо, щоб він з'явився, і fade-out, коли хочемо, щоб він зник.

const divElem = document.querySelector("div");
const htmlElem = document.querySelector(":root");

htmlElem.addEventListener("click", showHide);
document.addEventListener("keydown", showHide);

function showHide() {
  if (divElem.classList[0] === "fade-in") {
    divElem.classList.remove("fade-in");
    divElem.classList.add("fade-out");
  } else {
    divElem.classList.remove("fade-out");
    divElem.classList.add("fade-in");
  }
}

Результат

Цей код візуалізується так:

Дивіться також