Wolfenstein только на CSS это небольшой проект, который я сделал несколько недель назад. Это был эксперимент с 3D-трансформациями и анимацией CSS.
Вдохновленный демо FPS и другое Кодовая ручка Wolfenstein, я решил построить свою собственную версию. Он основан на Эпизоде 1 — Этаж 9 оригинальной 3D-игры Wolfenstein.
Редактор: Эта игра намеренно требует быстрой реакции, чтобы избежать экрана Game Over.
Вот видео прохождения:
Короче говоря, мой проект — это не что иное, как тщательно написанная длинная CSS-анимация. Плюс несколько экземпляров взлом флажка.
:checked ~ div { animation-name: spin; }
Среда состоит из 3D-граней сетки, а анимация в основном представляет собой простые 3D-перемещения и повороты. Ничего особенного.
Однако особенно сложно было решить две проблемы:
- Воспроизведение анимации «выстрела из оружия» всякий раз, когда игрок нажимает на врага.
- Когда быстро движущийся босс нанесет последний удар, начните драматическое замедленное движение.
На техническом уровне это означало:
- Воспроизвести анимацию, когда установлен следующий флажок.
- Замедлить анимацию, когда установлен флажок.
На самом деле, ни один из них не был правильно решен в моем проекте! Я либо использовал обходные пути, либо просто сдался.
С другой стороны, немного покопавшись, я в конце концов нашел ключ к обеим проблемам: изменение свойств запускаемой CSS-анимации. В этой статье мы продолжим изучение этой темы:
- Много интерактивных примеров.
- Dissections: как работает (или не работает) каждый пример?
- За кулисами: как браузеры обрабатывают состояния анимации?
Разрешите «Брось мои кирпичи».
Проблема 1: Воспроизведение анимации
Первый пример: «просто еще один флажок»
Моей первой интуицией было «просто добавить еще один флажок», который не работает:
Каждый флажок работает по отдельности, но не вместе. Если один флажок уже установлен, другой больше не работает.
Вот как это работает (или «не работает»):
- Ассоциация
animation-name
of<div>
isnone
по умолчанию. - Пользователь нажимает на один флажок,
animation-name
становитсяspin
, и анимация начинается сначала. - Через некоторое время пользователь нажимает на другой флажок. Новое правило CSS вступает в силу, но
animation-name
is все ещеspin
, что означает, что анимация не добавляется и не удаляется. Анимация просто продолжает воспроизводиться, как будто ничего не произошло.
Второй пример: «клонирование анимации»
Один из рабочих подходов — клонировать анимацию:
#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2; }
Вот как это работает:
animation-name
isnone
первоначально.- Пользователь нажимает «Вращать!»,
animation-name
становитсяspin1
. Анимацияspin1
запускается с самого начала, потому что он был только что добавлен. - Пользователь нажимает «Вращать снова!»,
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; }
Обратите внимание, что когда «Вращать снова!» установлен флажок, старая анимация бега становится второй анимацией в новом списке, что может быть неинтуитивно. Прямое следствие: трюк не сработает, если animation-fill-mode
is forwards
. Вот демо:
Если вам интересно, почему это так, вот несколько подсказок:
animation-fill-mode
isnone
по умолчанию, что означает «Анимация не имеет никакого эффекта, если не воспроизводится».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
используется в этом примере, т.е. не поддерживается всеми браузерами.
«Идеальное» решение
К предыдущему решению есть одна оговорка: «выход из слоумо» работает не очень хорошо.
Вот лучшее решение:
В этой версии замедленное движение можно плавно включать и выключать. Экспериментальная функция также не используется. Так это идеальное решение? Да и нет.
Это решение работает как «переключение передач»:
- Шестерни: есть две
<div>
с. Один является родителем другого. Оба имеютspin
анимация, но с другимanimation-duration
. Конечным состоянием элемента является накопление обеих анимаций. - Сдвиг: В начале только один
<div>
имеет свою анимацию. Другой поставлен на паузу. Когда флажок установлен, обе анимации меняются местами.
Хотя мне очень нравится результат, есть одна проблема: это хороший эксплойт spin
анимация, которая вообще не работает для других типов анимаций.
Практическое решение (с JS)
Для общей анимации можно реализовать функцию замедленного движения с помощью небольшого количества JavaScript:
Краткое объяснение:
- Пользовательское свойство используется для отслеживания хода анимации.
- Анимация «перезапускается» при установке флажка.
- Код JS вычисляет правильный
animation-delay
чтобы обеспечить плавный переход. Я рекомендую этой статье если вы не знакомы с отрицательными значениямиanimation-delay
.
Вы можете рассматривать это решение как гибрид «анимации перезапуска» и подхода «переключения передач».
Здесь важно правильно отслеживать ход анимации. Обходные пути возможны, если @property
не доступен. В качестве примера в этой версии используется z-index
для отслеживания прогресса:
Примечание: изначально я также пытался создать версию только для CSS, но безуспешно. Хотя не уверен на 100%, я думаю, что это потому, что animation-delay
is не анимируемый.
Вот версия с минимальным JavaScript. Работает только «вход в слоумо».
Пожалуйста, дайте мне знать, если вам удастся создать рабочую версию только для CSS!
Slow-mo Любая анимация (с JS)
Наконец, я хотел бы поделиться решением, которое работает (почти) для любой анимации, даже с несколькими сложными @keyframes
:
По сути, вам нужно добавить трекер прогресса анимации, а затем тщательно вычислить animation-delay
для новой анимации. Однако иногда может быть сложно (но возможно) получить правильные значения.
Например:
animation-timing-function
Неlinear
.animation-direction
Неnormal
.- несколько значений в
animation-name
с различнымиanimation-duration
Иanimation-delay
«S.
Этот метод также описан здесь для API веб-анимации.
Благодарности
Я пошел по этому пути после того, как столкнулся с проектами, основанными только на CSS. Некоторые были нежное произведение искусства, а некоторые были сложные приспособления. Мои любимые те, которые включают 3D-объекты, например, этот прыгающий мяч и это упаковочный куб.
В начале я понятия не имел, как они были сделаны. Позже я читал и многому научился у этот хороший учебник Аны Тюдор.
Как оказалось, создание и анимация 3D-объектов с помощью CSS мало чем отличается от того, что делается с помощью смеситель, только с немного другим вкусом.
Заключение
В этой статье мы рассмотрели поведение анимации CSS, когда animate-*
имущество изменено. В частности, мы разработали решения для «повторного воспроизведения анимации» и «замедленной анимации».
Я надеюсь, что вы найдете эту статью интересной. Пожалуйста, поделись своими мыслями!