Cover photo by Clem Onojeghuo via Unsplash.
I'm very sorry. And you're welcome.
Look at that bad boy chug. I think I set my CPU on fire making it... poor thing is really doing its best. I see why the CSS overlords didn't want to let me do this now.
Part one: Getting a text gradient ๐
You might notice this part of the code:
@mixin lead($one, $two, $three, $four, $five, $six) {
background: linear-gradient(80deg, $one, $two, $three, $four, $five, $six);
background-clip: text;
text-fill-color: transparent;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
That's what's generating the actual "rainbow text" part of this disaster.
The background
part is just generating the gradient itself; the good old rainbow barf you might have seen elsewhere before. Removing all the background-clip
and text-fill
shenanigans in that mixin makes it look like this:
It's all that other stuff that makes the text look like the background.
Normally the background-clip property is used to fine-tune the appearance of background around borders and paddings and the like, but the "text" value is highly magical.
The text-fill-color is roughly equivalent to your standard color
attribute. (For this pen you could just as easily substitute color: transparent
, I tried and it worked.) In this case we can set it to "transparent" so that our background shows through.
So what's going on here to get the "rainbow text" is:
- The background is turned into rainbows
- The background is clipped so that the only part of it that shows is the part that would normally be covered by text
- The text is made transparent so we can see the background through it
Part two: You can animate a background, but you can't make it linear-gradient
This is where everything started to go off the rails. My first approach was to slap a transition
on a single container and call it a day; it would have taken about five minutes, and most of that was googling how to make the gradient clip to the background.
However
Time to try another tactic.
<div id="main" class="container lead-red">
HAPPY PRIDE MONTH
</div>
<div id="fade" class="container lead-orange">
HAPPY PRIDE MONTH
</div>
Wait, why are there two-
๐ค
function intervalFunction() {
rainbowify();
setTimeout(intervalFunction, getNextTimeoutDuration());
};
intervalFunction();
What are you doing on an interval-
๐ค ... ๐ค ... ๐ค ...
Oh no.
Here's the gist:
Have two nearly identical HTML elements overlap each other. The first one, #main
, is on the bottom layer and is always visible; it "blinks" between gradients at a constant opacity. The second one, #fade
, is on the top layer and is constantly blinking in (when aligned) and fading out (to achieve an appearance of transition using opacity
).
These two are not on the same "rainbow cycle" - the #fade
text is ahead of the #main
by one color. JavaScript runs a loop using setInterval to juggle the classes on these two elements to keep the colors moving.
That also didn't work.
Part three: Blink in, fade out
My code looked something like this: During the main setInterval loop, attempt to halt animation with a .halt
class that sets the transition-timing property to 0ms (effectively disabling transitions). I would then set the opacity to 1 to get it to "blink in", and remove the .halt
class. The I would set the opacity back to 0 and let the transition do its magic. This all happened immediately, on about four lines of code.
Well, CSS transitions do not work that way. It turns out that in order for it to transition, the rendering engine needs a couple milliseconds to get its act together, regardless of what the transition property was on the element at the time.
Adding and then removing a class almost immediately is not, as it turns out, enough time.
I messed around with transition timing and other CSS for awhile before giving up and trying JavaScript. The initial JS hack of using a setTimeout( ... , 20)
inside my existing setInterval loop worked... about 95% of the time. Setting the timeout lower would cause it to stutter as the transitions couldn't keep up, and setting the timeout higher would cause highly noticeable pauses in the animation. However, having weird magic numbers lying around and occasional visits from Blinky McBlinkerton weren't where I wanted to leave it...
Part four: Reducing the juddering
The first part I wanted to eliminate was that magic 20ms timeout. I googled for an eternity and came up with this:
Trigger a reflow in between removing and adding the class name.
That explains this bit:
fade.classList.add("halt");
fade.classList.add("hide");
fade.classList.remove("lead-" + rainbow[(i + 1) % ilen]);
fade.classList.add("lead-" + rainbow[(i + 2) % ilen]);
void fade.offsetWidth; // <- this one!
fade.classList.remove("halt");
fade.classList.remove("hide");
The next weird thing I figured I'd plan ahead for was JS timer drift. I've seen this before when building schedules and clocks; one millisecond as specified is not always one millisecond in reality, so any interval will inevitably drift further and further away from accuracy. Since I have the same timeout hardcoded into my SCSS and my JS, it would definitely look nicer if they could consistently line up. This could prevent further pauses, stutters, etc. due to timer drift.
To do this, I use setTimeout instead of setInterval, and have the timeout call a function that calls another timeout (effectively creating an interval out of timeouts). Each timeout notes when it starts, and notes the "drift" from the last timeout, and corrects itself to attempt to more accurately hit a long-term target. This would definitely be useful if I were to pivot to something like @keyframes
.
In conclusion:
This is unnecessarily complicated and runs like molasses in winter. Just make a gif or something.
(ETA: Follow-up here. If for some reason you want to actually do this...)
But I did it, CSS overlords. I've beaten you. I've won.
Top comments (8)
You can create a very similar effect with less shenanigans by animating the background-position. You set the background-size to double the width of the element, then move it, like so
Obviously my animation is a little different as it runs forwards and backwards, instead of just in one direction, but I'm pretty confident you could work out how to make it go all in one way.
If I wanted to use your solution without modifying the animation itself, I'd probably try to constrain the gradient a little, because you get a lot of the green / yellow / orange part of the spectrum and not much of the red and purple parts.
For the unidirectional method, I'm thinking probably keyframes, so nice segue into CSS animations. I was already thinking of doing a part 2, so you gave me a pretty good idea for some other topics to hit on there.
I LOVE THIS!
Love it ๐ณ๏ธโ๐
Yes you did it! Next step: In WASM?Maybe itโs a litter less CPU hungry ?
But Kudos for all the hoops you had go around!
Wow, epic. I'm giving you an unicorn and a page mark, i need to read this in detail.
Loving it! I've saved it for a future project
when you said "here's the gist" i thought you meant "here's the gist" shows just how much github have already brainwashed me