CSS Puzzle: Animated frames
I made this neat demo with no javascript, check it out!
This is a really interesting puzzle: how can you animate text like this while preserving nested elements such as <span>
s with only a single keyframe so that things don't get out of sync?
It's pretty easy to get one of these conditions. You can preserve nested elements with multiple keyframes by just having a few separate elements that synchronously flash, like this:
<!DOCTYPE html>
<html>
<head>
<style>
@keyframes m1 {
0% { visibility: visible; }
50% { visibility: visible; }
50.001% { visibility: hidden; }
100% { visibility: hidden; }
}
@keyframes m2 {
0% { visibility: hidden; }
50% { visibility: hidden; }
50.001% { visibility: visible; }
100% { visibility: visible; }
}
#m1 {
animation: 2s m1 infinite;
}
#m2 {
animation: 2s m2 infinite;
}
</style>
</head>
<body>
<p id=m1><span style='color: #f00'>Hello world!</span></p>
<p id=m2><span style='color: #00f'>Goodbye world!</span></p>
</body>
</html>
Obviously these elements are offset, it's certainly possible to get this working properly, probably with some position: absolute
shenanigans, but that would make the code a bit more complicated, and I don't feel like writing that right now.
This does work, but if these two keyframes ever go out of sync, it could cause some visual artifacts. We could try using the before
and after
pseudo elements to combine everything into a single keyframe, like this:
<!DOCTYPE html>
<html>
<head>
<style>
@keyframes m {
0% { content: "Hello world!" }
50% { content: "Hello world!" }
50.001% { content: "Goodbye world!" }
100% { content: "Goodbye world!" }
}
#m::before {
animation: 2s m infinite;
content: "";
}
</style>
</head>
<body>
<p id=m></p>
</body>
</html>
Note: This code, and to a certain extent this entire article, was inspired by this truly awful clock that I saw on Reddit.
This is always in sync, but since our content is in a replaced element, we can't add span
s to change colors.
It would be really nice if every element could reference a single clock, like this pseudo-code:
define clock
forever {
clock = 0
sleep 1
clock = 1
sleep 1
}
element 0 {
if clock == 0
display: block
else
display: none
}
element 1 {
if clock == 1
display: block
else
display: none
}
We do sort of have if statements in CSS with the newly-introduced container queries. We can set the properties of some element based on the width of some parent element. We could then use the width of an element as a substitute for the clock, and just put all of our elements into a div
with position: absolute
so that the "width" of the grandparent element doesn't matter. Here's the final code that I wrote:
<!DOCTYPE html>
<html>
<head>
<style>
@keyframes snap {
0% { width: 1px; }
20% { width: 2px; }
40% { width: 3px; }
60% { width: 4px; }
80% { width: 5px; }
100% { width: 6px; }
}
#realbody {
position: absolute;
left: 0;
top: 0;
width: 100vw;
height: 100vw;
}
.msg {
animation: 2s snap infinite;
container-type: inline-size;
}
.hide {
display: none;
}
@container (min-width: 1px) {
#h0 {
display: block;
}
}
@container (min-width: 2px) {
#h0 {
display: none;
}
#h1 {
display: block;
}
}
@container (min-width: 3px) {
#h1 {
display: none;
}
#h2 {
display: block;
}
}
@container (min-width: 4px) {
#h2 {
display: none;
}
#h3 {
display: block;
}
}
@container (min-width: 5px) {
#h3 {
display: none;
}
#h4 {
display: block;
}
}
</style>
</head>
<body>
<div class=msg>
<div id=realbody>
<p class=hide id=h0>How can I do <span style='color:#f00'>this</span> 0?</p>
<p class=hide id=h1>How can I do <span style='color:#f0f'>this</span> 1?</p>
<p class=hide id=h2>How can I do <span style='color:#0f0'>this</span> 2?</p>
<p class=hide id=h3>How can I do <span style='color:#0ff'>this</span> 3?</p>
<p class=hide id=h4>How can I do <span style='color:#088'>this</span> 4?</p>
</div>
</div>
</pre>
</body>
</html>