Злом стану анімації CSS і часу відтворення PlatoBlockchain Data Intelligence. Вертикальний пошук. Ai.

Злом стану анімації CSS і часу відтворення

Wolfenstein лише для CSS це невеликий проект, який я зробив кілька тижнів тому. Це був експеримент із 3D-перетвореннями та анімацією CSS.

Натхненний Демо FPS і ще Wolfenstein CodePen, я вирішив створити власну версію. Він приблизно заснований на Епізоді 1 – Поверх 9 оригінальної гри Wolfenstein 3D.

Редактор: ця гра навмисно вимагає швидкої реакції, щоб уникнути екрана Game Over.

Ось відео про проходження:

[Вбудоване вміст]

У двох словах, мій проект — це не що інше, як ретельно написана довга CSS-анімація. Плюс кілька випадків прапорець зламати.

:checked ~ div { animation-name: spin; }

Середовище складається з граней тривимірної сітки, а анімація здебільшого є звичайним тривимірним перекладом і обертанням. Нічого особливого.

Однак вирішити дві проблеми було особливо складно:

  • Відтворюйте анімацію «стріляння зі зброї», коли гравець натискає на ворога.
  • Коли швидкий бос отримав останній удар, увімкніть драматичне сповільнене відтворення.

На технічному рівні це означало:

  • Повторне відтворення анімації, коли встановлено наступний прапорець.
  • Уповільнення анімації, коли встановлено прапорець.

Насправді жодне з них не було належним чином вирішено в моєму проекті! Я або скористався обхідними шляхами, або просто відмовився.

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

  • Багато інтерактивних прикладів.
  • Dissections: як кожен приклад працює (чи не працює)?
  • За кадром: як браузери обробляють стани анімації?

Дозволь мені «кидай мої цеглини».

Проблема 1: Повторне відтворення анімації

Перший приклад: «ще один прапорець»

Моєю першою інтуїцією було «просто додайте ще один прапорець», що не працює:

Кожен прапорець працює окремо, але не обидва разом. Якщо один прапорець уже встановлено, інший більше не працює.

Ось як це працює (або «не працює»):

  1. Команда animation-name of <div> is none за замовчуванням
  2. Користувач натискає один прапорець, animation-name стає spin, і анімація починається спочатку.
  3. Через деякий час користувач натискає інший прапорець. Нове правило CSS вступає в силу, але animation-name is як і раніше spin, що означає, що анімація не додається та не видаляється. Анімація просто продовжує відтворюватися, ніби нічого не сталося.

Другий приклад: «клонування анімації»

Одним з робочих підходів є клонування анімації:

#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2; }

Ось як це працює:

  1. animation-name is none спочатку.
  2. Користувач натискає «Оберти!», animation-name стає spin1. Анімація spin1 розпочато з самого початку, оскільки його щойно додали.
  3. Користувач натискає «Оберни знову!», animation-name стає spin2. Анімація spin2 розпочато з самого початку, оскільки його щойно додали.

Зауважте, що на кроці №3 spin1 видаляється через порядок правил CSS. Це не спрацює, якщо «Оберни знову!» перевіряється спочатку.

Третій приклад: «додавання тієї самої анімації»

Інший робочий підхід полягає в «додаванні тієї самої анімації»:

#spin1:checked ~ div { animation-name: spin; }
#spin2:checked ~ div { animation-name: spin, spin; }

Це схоже на попередній приклад. Ви насправді можете зрозуміти цю поведінку таким чином:

#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2, spin1; }

Зауважте, що коли напис "Spin again!" позначено, стара запущена анімація стає другою анімацією в новому списку, що може бути неінтуїтивним. Прямий наслідок: трюк не спрацює, якщо animation-fill-mode is forwards. Ось демо:

Якщо вам цікаво, чому це так, ось кілька підказок:

  • animation-fill-mode is none за замовчуванням, що означає «Анімація не має жодного ефекту, якщо не відтворюється».
  • animation-fill-mode: forwards; означає «Після завершення відтворення анімації вона має залишатися на останньому ключовому кадрі назавжди».
  • spin1рішення завжди скасовуються spin2це тому, що spin1 з’являється пізніше в списку.
  • Припустімо, що користувач натискає «Оберти!», чекає повного обертання, а потім натискає «Оберти знову!». Зараз. spin1 вже закінчено, і spin2 тільки починається.

Обговорення

Емпіричне правило: ви не можете «перезапустити» наявну анімацію CSS. Натомість ви хочете додати та відтворити нову анімацію. Це може бути підтверджено специфікація W3C:

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

Порівнюючи останні два приклади, я думаю, що на практиці «клонування анімації» часто має працювати краще, особливо коли доступний препроцесор CSS.

Проблема 2: Уповільнений рух

Хтось може подумати, що уповільнення анімації – це лише питання встановлення довшого animation-duration:

div { animation-duration: 0.5s; }
#slowmo:checked ~ div { animation-duration: 1.5s; }

Дійсно, це працює:

… чи так?

За допомогою кількох налаштувань проблему буде легше побачити.

Так, анімація сповільнена. І ні, це не виглядає добре. Собака (майже) завжди «стрибає», коли ви перемикаєте прапорець. Крім того, здається, що собака стрибає у випадкове положення, а не в початкове. Як так?

Це було б легше зрозуміти, якби ми ввели два «елементи тіні»:

Обидва елементи тіні запускають однакову анімацію з різними animation-duration. І на них не впливає прапорець.

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

Цитувати специфікація W3C:

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

Це випливає з без громадянства дизайн, який дозволяє браузерам легко визначати значення анімації. Описано фактичний розрахунок тут та тут.

Чергова спроба

Одна з ідей полягає в тому, щоб призупинити поточну анімацію, а потім додати повільнішу анімацію, яка буде виконуватися звідти:

div {
  animation-name: spin1;
  animation-duration: 2s;
}

#slowmo:checked ~ div {
  animation-name: spin1, spin2;
  animation-duration: 2s, 5s;
  animation-play-state: paused, running;
}

Так це працює:

… чи так?

Він сповільнюється, коли ви натискаєте «Slowmo!». Але якщо ви дочекаєтеся повного кола, ви побачите «стрибок». Насправді, він завжди переходить у положення, коли "Slowmo!" натиснуто.

Причина в тому, що у нас немає from визначено ключовий кадр – і ми не повинні. Коли користувач натискає «Slowmo!», spin1 зупиняється на певній позиції, і spin2 починається точно з того самого положення. Ми просто не можемо передбачити цю позицію заздалегідь ... чи можемо?

Працююче рішення

Ми можемо! Використовуючи спеціальну властивість, ми можемо зафіксувати кут у першій анімації, а потім передати його до другої анімації:

div {
  transform: rotate(var(--angle1));
  animation-name: spin1;
  animation-duration: 2s;
}

#slowmo:checked ~ div {
  transform: rotate(var(--angle2));
  animation-name: spin1, spin2;
  animation-duration: 2s, 5s;
  animation-play-state: paused, running;
}

@keyframes spin1 {
  to {
    --angle1: 360deg;
  }
}

@keyframes spin2 {
  from {
    --angle2: var(--angle1);
  }
  to {
    --angle2: calc(var(--angle1) + 360deg);
  }
}

Примітка: @property використовується в цьому прикладі, який є не підтримується всіма браузерами.

«Ідеальне» рішення

До попереднього рішення є застереження: «вихід із slowmo» не працює належним чином.

Ось краще рішення:

У цій версії можна плавно ввімкнути або вийти з повільного відтворення. Також не використовується експериментальна функція. То чи це ідеальне рішення? Так і ні.

Це рішення працює як «перемикання» «передач»:

  • Шестерні: є дві <div>с. Один є батьком іншого. Обидва мають spin анімація, але з іншою animation-duration. Кінцевим станом елемента є накопичення обох анімацій.
  • Перемикання: на початку лише одне <div> працює анімація. Інший призупинено. Коли прапорець перемикається, обидві анімації міняються місцями.

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

Практичне рішення (з JS)

Для загальних анімацій можна досягти функції уповільненого руху за допомогою невеликої кількості JavaScript:

Швидке пояснення:

  • Спеціальна властивість використовується для відстеження прогресу анімації.
  • Анімація «перезапускається», якщо встановити прапорець.
  • Код JS обчислює правильні animation-delay щоб забезпечити плавний перехід. я рекомендую цю статтю якщо ви не знайомі з від’ємними значеннями animation-delay.

Ви можете розглядати це рішення як гібрид «перезапуску анімації» та підходу «перемикання передач».

Тут важливо правильно відслідковувати хід анімації. Обхідні шляхи можливі, якщо @property недоступний. Як приклад, ця версія використовує z-index щоб відстежувати прогрес:

Додаткова примітка: спочатку я також намагався створити версію лише для CSS, але не вдалося. Хоча я не впевнений на 100%, я думаю, що це тому animation-delay is не анімований.

Ось версія з мінімальним JavaScript. Працює лише «введення slowmo».

Будь ласка, дайте мені знати, якщо вам вдасться створити робочу версію лише для CSS!

Уповільнена будь-яка анімація (з JS)

Нарешті, я хотів би поділитися рішенням, яке працює для (майже) будь-якої анімації, навіть із кількома складними @keyframes:

По суті, вам потрібно додати трекер прогресу анімації, а потім ретельно обчислити animation-delay для нової анімації. Однак іноді може бути складно (але можливо) отримати правильні значення.

Наприклад:

  • animation-timing-function НЕ linear.
  • animation-direction НЕ normal.
  • кілька значень в animation-name з різними animation-durationі с animation-delayс.

Цей спосіб також описаний тут для API веб-анімації.

Подяки

Я почав йти цим шляхом після того, як зіткнувся з проектами лише на CSS. Деякі були делікатний твір мистецтва, а деякі були складні пристосування. Мої улюблені ті, що включають 3D-об’єкти, наприклад, це стрибаючий м'яч і це пакувальний куб.

На початку я поняття не мав, як це було зроблено. Пізніше я прочитав і багато чому навчився цей гарний підручник Ани Тудор.

Як виявилося, створення та анімація 3D-об'єктів за допомогою CSS мало чим відрізняється від того, як це робити за допомогою змішувач, тільки з трохи іншим смаком.

Висновок

У цій статті ми розглянули поведінку CSS-анімації, коли an animate-* майно змінено. Особливо ми розробили рішення для «повторного відтворення анімації» та «уповільненої анімації».

Сподіваюся, ця стаття буде вам цікава. Будь ласка, дай мені знати твої думки!

Часова мітка:

Більше від CSS-хитрощі