CSS-only Wolfenstein is a little bit undertaking that I made a number of weeks in the past. It was an experiment with CSS 3D transformations and animations.
Impressed by the FPS demo and one other Wolfenstein CodePen, I made a decision to construct my very own model. It’s loosely based mostly on Episode 1 – Flooring 9 of the unique Wolfenstein 3D sport.
Editor: This sport deliberately requires some fast response to keep away from a Recreation Over display.
Here’s a playthrough video:
In a nutshell, my undertaking is nothing however a rigorously scripted lengthy CSS animation. Plus a number of situations of the checkbox hack.
:checked ~ div { animation-name: spin; }
The setting consists of 3D grid faces and the animations are principally plain 3D translations and rotations. Nothing actually fancy.
Nevertheless, two issues have been notably tough to unravel:
- Play the “weapon firing” animation every time the participant clicks on an enemy.
- When the fast-moving boss bought the final hit, enter a dramatic sluggish movement.
At a technical-level, this meant:
- Replay an animation when the following checkbox is checked.
- Decelerate an animation, when a checkbox is checked.
The truth is, neither was correctly solved in my undertaking! I both ended up utilizing workarounds or simply gave up.
However, after some digging, finally I discovered the important thing to each issues: altering the properties of operating CSS animations. On this article, we are going to discover additional on this subject:
- A lot of interactive examples.
- Dissections: how does every instance work (or not work)?
- Behind-the-scene: how do browsers deal with animation states?
Let me “toss my bricks”.
Drawback 1: Replaying Animation
The primary instance: “simply one other checkbox”
My first instinct was “simply add one other checkbox”, which doesn’t work:
Every checkbox works individually, however not each collectively. If one checkbox is already checked, the opposite now not works.
Right here’s the way it works (or “doesn’t work”):
- The
animation-name
of<div>
isnone
by default. - The consumer clicks on one checkbox,
animation-name
turns intospin
, and the animation begins from the start. - After some time, the consumer clicks on the opposite checkbox. A brand new CSS rule takes impact, however
animation-name
is nonethelessspin
, which implies no animation is added nor eliminated. The animation merely continues enjoying as if nothing occurred.
The second instance: “cloning the animation”
One working strategy is to clone the animation:
#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2; }
Right here’s the way it works:
animation-name
isnone
initially.- The consumer clicks on “Spin!”,
animation-name
turns intospin1
. The animationspin1
is began from the start as a result of it was simply added. - The consumer clicks on “Spin once more!”,
animation-name
turns intospin2
. The animationspin2
is began from the start as a result of it was simply added.
Be aware that in Step #3, spin1
is eliminated due to the order of the CSS guidelines. It received’t work if “Spin once more!” is checked first.
The third instance: “appending the identical animation”
One other working strategy is to “append the identical animation”:
#spin1:checked ~ div { animation-name: spin; }
#spin2:checked ~ div { animation-name: spin, spin; }
That is just like the earlier instance. You may really perceive the conduct this manner:
#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2, spin1; }
Be aware that when “Spin once more!” is checked, the outdated operating animation turns into the second animation within the new record, which might be unintuitive. A direct consequence is: the trick received’t work if animation-fill-mode
is forwards
. Right here’s a demo:
When you surprise why that is the case, listed below are some clues:
animation-fill-mode
isnone
by default, which implies “The animation has no impact in any respect if not enjoying”.animation-fill-mode: forwards;
means “After the animation finishes enjoying, it should keep on the final keyframe perpetually”.spin1
’s resolution at all times overridespin2
’s as a result ofspin1
seems later within the record.- Suppose the consumer clicks on “Spin!”, waits for a full spin, then clicks on “Spin once more!”. At this second.
spin1
is already completed, andspin2
simply begins.
Dialogue
Rule of thumb: you can not “restart” an current CSS animation. As an alternative, you need to add and play a brand new animation. This can be confirmed by the W3C spec:
As soon as an animation has began it continues till it ends or the animation-name is eliminated.
Now evaluating the final two examples, I believe in follow, “cloning animations” ought to typically work higher, particularly when CSS preprocessor is on the market.
Drawback 2: Gradual Movement
One would possibly assume that slowing an animation is only a matter of setting an extended animation-duration
:
div { animation-duration: 0.5s; }
#slowmo:checked ~ div { animation-duration: 1.5s; }
Certainly, this works:
… or does it?
With a number of tweaks, it must be simpler to see the problem.
Sure, the animation is slowed down. And no, it doesn’t look good. The canine (nearly) at all times “jumps” if you toggle the checkbox. Moreover, the canine appears to leap to a random place slightly than the preliminary one. How come?
It might be simpler to know it if we launched two “shadow parts”:
Each shadow parts are operating the identical animations with totally different animation-duration
. And they aren’t affected by the checkbox.
While you toggle the checkbox, the aspect simply instantly switches between the states of two shadow parts.
Quoting the W3C spec:
Adjustments to the values of animation properties whereas the animation is operating apply as if the animation had these values from when it started.
This follows the stateless design, which permits browsers to simply decide the animated worth. The precise calculation is described right here and right here.
One other Try
One concept is to pause the present animation, then add a slower animation that takes over from there:
div {
animation-name: spin1;
animation-duration: 2s;
}
#slowmo:checked ~ div {
animation-name: spin1, spin2;
animation-duration: 2s, 5s;
animation-play-state: paused, operating;
}
So it really works:
… or does it?
It does decelerate if you click on on “Slowmo!”. However in case you watch for a full circle, you will note a “leap”. Really, it at all times jumps to the place when “Slowmo!” is clicked on.
The reason being we don’t have a from
keyframe outlined – and we shouldn’t. When the consumer clicks on “Slowmo!”, spin1
is paused at some place, and spin2
begins at precisely the identical place. We merely can not predict that place beforehand … or can we?
A Working Resolution
We are able to! Through the use of a customized property, we are able to seize the angle within the first animation, then move it to the second animation:
div {
rework: rotate(var(--angle1));
animation-name: spin1;
animation-duration: 2s;
}
#slowmo:checked ~ div {
rework: rotate(var(--angle2));
animation-name: spin1, spin2;
animation-duration: 2s, 5s;
animation-play-state: paused, operating;
}
@keyframes spin1 {
to {
--angle1: 360deg;
}
}
@keyframes spin2 {
from {
--angle2: var(--angle1);
}
to {
--angle2: calc(var(--angle1) + 360deg);
}
}
Be aware: @property
is used on this instance, which is not supported by all browsers.
The “Excellent” Resolution
There’s a caveat to the earlier resolution: “exiting slowmo” doesn’t work nicely.
Here’s a higher resolution:
On this model, sluggish movement might be entered or exited seamlessly. No experimental function is used both. So is it the proper resolution? Sure and no.
This resolution works like “shifting” “gears”:
- Gears: there are two
<div>
s. One is the mum or dad of the opposite. Each have thespin
animation however with totally differentanimation-duration
. The ultimate state of the aspect is the buildup of each animations. - Shifting: Initially, just one
<div>
has its animation operating. The opposite is paused. When the checkbox is toggled, each animations swap their states.
Whereas I actually just like the consequence, there’s one downside: it’s a good exploit of the spin
animation, which doesn’t work for different kinds of animations usually.
A Sensible Resolution (with JS)
For basic animations, it’s doable to realize the sluggish movement perform with a little bit of JavaScript:
A fast clarification:
- A customized property is used to trace the animation progress.
- The animation is “restarted” when the checkbox is toggled.
- The JS code computes the proper
animation-delay
to make sure a seamless transition. I like to recommend this text if you’re not acquainted with adverse values ofanimation-delay
.
You may view this resolution as a hybrid of “restarting animation” and the “gear-shifting” strategy.
Right here you will need to observe the animation progress appropriately. Workarounds are doable if @property
shouldn’t be out there. For example, this model makes use of z-index
to trace the progress:
Facet-note: initially, I additionally tried to create a CSS-only model however didn’t succeed. Whereas not 100% certain, I believe it’s as a result of animation-delay
is not animatable.
Here’s a model with minimal JavaScript. Solely “getting into slowmo” works.
Please let me know in case you handle to create a working CSS-only model!
Gradual-mo Any Animation (with JS)
Lastly, I’d prefer to share an answer that works for (nearly) any animation, even with a number of difficult @keyframes
:
Principally, that you must add an animation progress tracker, then rigorously compute animation-delay
for the brand new animation. Nevertheless, generally it might be tough (however doable) to get the proper values.
For instance:
animation-timing-function
shouldn’t belinear
.animation-direction
shouldn’t beregular
.- a number of values in
animation-name
with totally differentanimation-duration
’s andanimation-delay
’s.
This methodology can also be described right here for the Internet Animations API.
Acknowledgments
I began down this path after encountering CSS-only initiatives. Some have been delicate art work, and a few have been complicated contraptions. My favorites are these involving 3D objects, for instance, this bouncing ball and this packing dice.
To start with, I had no clue how these have been made. Later I learn and discovered lots from this good tutorial by Ana Tudor.
Because it turned out, constructing and animating 3D objects with CSS shouldn’t be a lot totally different from doing it with Blender, simply with a bit totally different taste.
Conclusion
On this article we examined the conduct of CSS animations when an animate-*
property is altered. Particularly we labored out options for “replaying an animation” and “animation slow-mo”.
I hope you discover this text attention-grabbing. Please let me know your ideas!