CSS-only DVD Screensaver animation
A visual walkthrough
Five minutes later, my mind had already wandered into ”how would I animate the bouncing DVD logo” territory.
My first thought was that Javascript was required to figure out when the collisions with the edges of the screen would happen, otherwise I would not be able to change the color.
Thus, I set out to prove myself wrong. In this article I'll go through my thought process and how I got it to work with just HTML and CSS.
My strategy to reverse engineer an animation is always the same: good old divide and conquer:
Let's focus on the movement.
Can we divide it further? Yes, with one of the most useful strategies in the animator's toolkit: the bouncing animation can be split off into independent movements across the horizontal and vertical axes.
That's simple enough to animate. Let's write some code!
:root {
Let's set our desired container and logo sizes, as well as the animation speed, using CSS variables:
--container-w: 320; /* no units so we can do division */
--container-h: 180;
--logo-w: 40;
--logo-h: 20;
--speed: 90; /* pixels per second */
We can easily calculate how long it takes for the logo to move through the screen, both horizontally and vertically.
--duration-x: calc(
(var(--container-w) - var(--logo-w)) / var(--speed) * 1s
);
--duration-y: calc(
(var(--container-h) - var(--logo-h)) / var(--speed) * 1s
);
}
Assume the container element has relative
positioning and the appropriate size. We then set the properties for the logo and animate left
and top
in a linear fashion, forever, alternating back and forth.
.logo {
position: absolute;
width: calc(var(--logo-w) * 1px);
height: calc(var(--logo-h) * 1px);
animation:
x var(--duration-x) linear infinite alternate,
y var(--duration-y) linear infinite alternate;
}
@keyframes x {
from { left: 0; }
to { left: calc(100% - var(--logo-w)); }
}
@keyframes y {
from { top: 0; }
to { top: calc(100% - var(--logo-h)); }
}
If we only needed to change colors when the logo hit the left or right wall, this idea would work:
First, define keyframes to change color at regular intervals, five of them in this case. I'm using equispaced hue values on the HSL color spectrum.
@keyframes colorX {
from { background: hsl(0deg 100% 50%); }
20% { background: hsl(72deg 100% 50%); }
40% { background: hsl(144deg 100% 50%); }
60% { background: hsl(216deg 100% 50%); }
80% { background: hsl(288deg 100% 50%); }
to { background: hsl(0deg 100% 50%); }
}
Then we just need to add a new animation with the appropriate duration and the step-start
timing function so the value "jumps" once every --duration-x
.
.logo {
animation:
x var(--duration-x) linear infinite alternate,
y var(--duration-y) linear infinite alternate,
colorX calc(var(--duration-x) * 5) step-start infinite;
}
We animate two CSS variables, then define our hue
based on a linear combination of both.
Animate CSS variables? Can you even do that?
Well, yes, but it's something you hardly ever see because they don't behave how you expect.
In most cases, animating a CSS variable is about as useful as animating any non-interpolable property such as display
or visibility
.
.animate-width-variable {
width: calc(var(--width) * 1%);
animation: frames 1s linear infinite alternate;
}
@keyframes frames {
from { --width: 10; }
to { --width: 100;
}
This is the result:
As you can see, it's not a smooth one. It just goes from one value to the next with no interpolation.
Thing is... we don't need smooth interpolation, we're using step-start
timing anyway.
@keyframes colorX {
from { --color--x: 0; }
20% { --color-x: 2; }
40% { --color-x: 4; }
60% { --color-x: 1; }
80% { --color-x: 3; }
to { --color-x: 0; }
}
@keyframes colorY {
from { --color-y: 0; }
20% { --color-y: 2; }
40% { --color-y: 4; }
60% { --color-y: 1; }
80% { --color-y: 3; }
to { --color-y: 0; }
}
.logo {
animation:
x var(--duration-x) linear infinite alternate,
y var(--duration-y) linear infinite alternate,
colorX calc(var(--duration-x) * 5) step-start infinite,
colorY calc(var(--duration-y) * 5) step-start infinite;
color:
hsl(calc(
360 / 25 * (var(--color-y) * 5 + var(--color-x))
) 100% 50%);
}
There's a total of 5x5=25
possible colors, equispaced hue values ranging from 0deg
when --color-x
and --color-y
are both 0
, to 345.6deg
when they're both 4
.
I could've used 0-1-2-3-4
for the variables, but if we increment in a star pattern (0-2-4-1-3
), each color jump is a bit more noticeable.
I hope you both enjoyed the article and learned something you might use for a more practical project.
Here's a Codepen link to the final result.
And just because I can't fathom finishing this article without even mentioning corner hits, I've made a version with interactive parameters and a corner collision calculator. See it in the next slide.
Sorry, the interactive experience is not available on smaller screens.
Use the sliders to change the animation parameters.
--container-w: 480;
--container-h: 360;
--logo-w: 25%;
--speed: 90;
Warning: while you drag, the logo may flash rapidly.
Corner hit every 20.00 seconds.
Note
In the future you may see more animated CSS properties out in the wild thanks to the new @property
syntax. Interpolation is still not supported on Firefox as of February 2024.
As a matter of fact, I'm already using CSS variable interpolation in the animated background of this site. The background defaults to a static version on browsers that don't support it.
Sources
CRT display effect on first slide adapted from this article by Alec Lownes.
Corner hit calculations by David Vreken at The Lost Math Lessons.
- "Five minutes later" card from SpongeBob Time Cards Generator by Gavin Rehkemper.
Published on February 14, 2024.
Read the sequel where I build a corner hit timer.