I was killing some time browsing my CodePen feed for some eye candy and didn’t need to go past the first page before spotting a neat CSS hover effect by Adam Argyle.
I must’ve spent 10 minutes just staring at the demo in awe. There’s something about this that feels so app-like. I think it might be how contextually accurate it is in that the background color slides in from the left, then exits out through the right. It’s exactly the sort of behavior I’d expect from a mouse-in, mouse-out sort of interaction.
Whatever the case, I fired up a fresh pen and went to work recreating it. And it’s not super complex or anything, but rather a clever use of transitions and transforms paired with proper offsets. Quite elegant! I’m actually a little embarrassed how long it took me to realize how the mouse-out part works.
Here’s how I tackled it, warts and all.
“I bet that’s using a transition on a background
.”
Table of Contents
That was my first thought. Define the background-color, set the background-size
and background-position
, then transition the background-position
. That’s how I’ve seen that “growing” background color thing done in the past. I’ve done that myself on some projects, like this:
If I could do the same thing, only from left-to-right, then all that’s left is the mouse-out, right? Nope. The problem is there’s nothing that can really make the background-position
transition from left-to-right to left-to-right. I could make it do one or the other, but not both.
“Maybe it’s a transform
instead.”
My next attempt was jump into transforms. The transform
property provides a bunch of functions that can transition
together for slightly more complex movement. For example, the background
can “grow” or “shrink” by changing the element’s scale()
. Or, in this case, just along the x-axis with scaleX()
.
But like I mentioned, there isn’t a way to isolate the element’s background
to do that. Going from scaleX(0)
to scaleX(1)
scales the entire element, so that basically squishes the link — content and all — down to nothing, then stretches it back out to its natural size which is a totally different effect. Plus, it means starting with scaleX(0)
which hides the whole dang thing by default making it unusable.
But a pseudo-element could work! It doesn’t matter if that gets squished or hidden because it isn’t part of the actual content. Gotta put the background
on that instead and position it directly under the link.
a { /* Keeps the pseudo-element contained to the element */ position: relative;
} a::before { background: #ff9800; content: ""; inset: 0; /* Logical equivalent to physical offsets */ position: absolute; transform: scaleX(0); /* Hide by default */ z-index: -1; /* Ensures the link is stacked on top */
}
“Now I need ::before
to change on hover.”
I knew I could make ::before
scale from 0
to 1
by chaining it to the link element’s :hover
state.
a:hover::before { transform: scaleX(1)
}
Nice! I was onto something.
Sprinkle a little transition
fairy dust on it and things start to come to life.
a::before { background: #ff9800; content: ""; inset: 0; position: absolute; transform: scaleX(0); transition: transform .5s ease-in-out; z-index: -1;
}
“Hmm, the transition moves in both directions.”
Again, this is where I sorta got stuck. Something in my head just wasn’t clicking for some reason. As per usual, I ran over to the CSS-Tricks Almanac to see what property might’ve slipped my mind.
Ah, yes. That would be transform-origin
. That allows me to set where the transform
starts, which is not totally dissimilar from setting the background-position
like I tried earlier. The transform
could start from the left instead of its default 50% 50%
position.
a::before { background: #ff9800; content: ""; inset: 0; position: absolute; transform: scaleX(0); transform-origin: left; transition: transform .5s ease-in-out; z-index: -1;
}
Yeah, like this:
I was already transitioning ::before
to scaleX(1)
on link hover. If I reversed the transform-origin
from left
to right
at the same time, then mayyyybe the highlight goes out the opposite of how it came in when the mouse exits?
a:hover::before { transform: scaleX(1); transform-origin: right;
}
????
Whoops, backwards! Let’s swap the left
and right
values. ????
Gorgeous. Thank you, Adam, for the inspiration!