Animating Text Color
snippetWe started out with a
linear-gradient animation
for :focus
, :hover
and :active
:
That looks decent, but feels a little weird when activating adjacent links, as the animation’s direction doesn’t adapt to that kinda contextual information.
So I tweaked the technique to use a radial gradient, with the effect emanating from the center instead:
If you wanna get psychedelic fancy, you might even use multiple colors:
Safari appears to include text underlines when clipping the background while
other browsers don’t. There’s also a confounding layout shift during the
radial animation which I have no idea how to address
Roma Komarov identified
as an already-fixed bug, along with a @property
-based workaround.
The linear technique works by making text transparent and painting a hard-stop
gradient in its stead – positioned so only one half is visible. Assuming you
have a link <a class="text-flood" …>
(for lack of a more descriptive name 🤷 ):
.text-flood {
color: transparent;
background-image: linear-gradient(to right,
var(--text-flood-color-alt) 50%,
var(--text-flood-color) 50%);
background-size: 200% 100%;
background-position: 100%;
background-clip: text;
}
(Note that --text-flood-color
and --text-flood-color-alt
need to be defined
somewhere.)
When the respective link is activated, we shift that background image’s position to reveal the other half:
.text-flood {
/* … */
@media (prefers-reduced-motion: no-preference) {
transition: background-position 200ms linear;
}
&:focus,
&:hover,
&:active {
background-position: 0 100%;
}
}
The radial equivalent works much the same way, except there we animate a custom property:
.text-flood {
--text-flood-offset: 0%;
color: transparent;
background-image: radial-gradient(circle at 50%,
var(--text-flood-color-alt) var(--text-flood-offset),
var(--text-flood-color) var(--text-flood-offset));
background-clip: text;
@media (prefers-reduced-motion: no-preference) {
transition: --text-flood-offset 200ms linear;
}
&:focus,
&:hover,
&:active {
--text-flood-offset: 100%;
}
}
@property --text-flood-offset {
syntax: "<percentage>";
inherits: true;
initial-value: 0%;
}
Note that such animations require
@property
support,
though it works as a progressive enhancement: The fallback is just skipping the
transition, switching states instantly – just like for users who
prefer reduced motion.
Support for background-clip: text
, on the other hand, might be a little more
problematic: While it’s been
reasonably well supported
for a couple of years, there’s no acceptable fallback story; text just
disappears. Thus it seems prudent to conditionally employ this effect with a
feature query:
@supports (background-clip: text) {
.text-flood {
/* … */
}
}