Let’s create a pure CSS effect that changes the color of a text link on hover… but slide that new color in instead of simply swapping colors.
There are four different techniques we can use to do this. Let’s look at those while being mindful of important things, like accessibility, performance, and browser support in mind.
Let’s get started!
Technique 1: Using background-clip: text
At the time of writing, the background-clip: text
property is an experimental feature and is not supported in Internet Explorer 11 and below.
This technique involves creating knockout text with a hard stop gradient. The markup consists of a single HTML link (<a>
) element to create a hyperlink:
<a href="#">Link Hover</a>
We can start adding styles to the hyperlink. Using overflow: hidden
will clip any content outside of the hyperlink during the hover transition:
a {
position: relative;
display: inline-block;
font-size: 2em;
font-weight: 800;
color: royalblue;
overflow: hidden;
}
We will need to use a linear gradient with a hard stop at 50% to the starting color we want the link to be as well as the color that it will change to:
a {
/* Same as before */
background: linear-gradient(to right, midnightblue, midnightblue 50%, royalblue 50%);
}
Let’s use background-clip
to clip the gradient and the text
value to display the text. We will also use the background-size
and background-position
properties to have the starting color appear:
a {
/* Same as before */
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-size: 200% 100%;
background-position: 100%;
}
Finally, let’s add the transition
CSS property and :hover
CSS pseudo-class to the hyperlink. To have the link fill from left to right on hover, use the background-position
property:
a {
/* Same as before */
transition: background-position 275ms ease;
}
a:hover {
background-position: 0 100%;
}
While this technique does achieve the hover effect, Safari and Chrome will clip text decorations and shadows, meaning they won’t be displayed. Applying text styles, such as an underline, with the text-decoration CSS property will not work. Perhaps consider using other approaches when creating underlines.
Technique 2: Using width/height
This works by using a data attribute containing the same text as the one in the <a>
tag and setting the width
(filling the text from left-to-right or right-to-left) or height
(filling the text from top-to-bottom or bottom-to-top), from 0% to 100% on hover.
Here is the markup:
<a href="#" data-content="Link Hover">Link Hover</a>
The CSS is similar to the previous technique minus the background CSS properties. The text-decoration
property will work here:
a {
position: relative;
display: inline-block;
font-size: 2em;
color: royalblue;
font-weight: 800;
text-decoration: underline;
overflow: hidden;
}
This is when we need to use the content from the data-content
attribute. It will be positioned above the content in the <a>
tag. We get to use the nice little trick of copying the text in the data attribute and displaying it via the attr()
function on the content property of the element’s ::before
pseudo-element.
a::before {
position: absolute;
content: attr(data-content); /* Prints the value of the attribute */
top: 0;
left: 0;
color: midnightblue;
text-decoration: underline;
overflow: hidden;
transition: width 275ms ease;
}
To keep the text from wrapping to the next line, white-space: nowrap
will be applied. To change the link fill color, set the value for the color CSS property using the ::before
pseudo-element and having the width
start at 0:
a::before {
/* Same as before */
width: 0;
white-space: nowrap;
}
Increase the width to 100% to the ::before
pseudo element to complete the text effect on hover:
a:hover::before {
width: 100%;
}
While this technique does the trick, using the width
or height
properties will not produce a performant CSS transition. It is best to use either the transform
or opacity
properties to achieve a smooth, 60fps transition.
Using the text-decoration
CSS property can allow for different underline styles to appear in the CSS transition. I created a demo showcasing this using the next technique: the clip-path
CSS property.
Technique 3: Using clip-path
For this technique, we will be using the clip-path
CSS property with a polygon shape. The polygon will have four vertices, with two of them expanding to the right on hover:
The markup is the same as the previous technique. We will use a ::before
pseudo-element again, but the CSS is different:
a::before {
position: absolute;
content: attr(data-content);
color: midnightblue;
text-decoration: underline;
clip-path: polygon(0 0, 0 0, 0% 100%, 0 100%);
transition: clip-path 275ms ease;
}
Unlike the previous techniques, text-decoration: underline
must be declared to the ::before pseudo-element
for the color to fill the underline on hover.
Now let’s look into the CSS for the clip-path
technique:
clip-path: polygon(0 0, 0 0, 0% 100%, 0 100%);
The polygon’s vertices of the clip-path
property are set in percentages to define coordinates by the order written:
0 0
= top left0 0
= top right100% 0
= bottom right0 100%
= bottom left
The direction of the fill effect can be changed by modifying the coordinates. Now that we have an idea for the coordinates, we can make the polygon expand to the right on hover:
a:hover::before {
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}
This technique works pretty well, but note that support for the clip-path
property varies between browsers. Creating a CSS transition with clip-path
is a better alternative than using the width
/height
technique; however, it does affect the browser paint.
Technique 4: Using transform
The markup for this technique uses a masking method with a <span>
element. Since we will be using duplicated content in a separate element, we will use aria-hidden="true"
to improve accessibility — that will hide it from screen readers so the content isn’t read twice:
<a href="#"><span data-content="Link Hover" aria-hidden="true"></span>Link Hover</a>
The CSS for the <span>
element contains a transition that will be starting from the left:
span {
position: absolute;
top: 0;
left: 0;
overflow: hidden;
transform: translateX(-100%);
transition: transform 275ms ease;
}
Next, we need to get the <span>
to slide the right like this:
To do this, we will use the translateX()
CSS function and set it to 0:
a:hover span {
transform: translateX(0);
}
Then, we will use the ::before
pseudo-element for the <span>
, again using the data-content
attribute we did before. We’ll set the position by translating it 100% along the x-axis.
span::before {
display: inline-block;
content: attr(data-content);
color: midnightblue;
transform: translateX(100%);
transition: transform 275ms ease;
text-decoration: underline;
}
Much like the <span>
element, the position of the ::before
pseudo-element will also be set to translateX(0)
:
a:hover span::before {
transform: translateX(0);
}
While this technique is the the most cross-browser compatible of the bunch, it requires more markup and CSS to get there. That said, using the transform CSS property is great for performance as it does not trigger repaints and thus produces smooth, 60fps CSS transitions.
There we have it!
We just looked at four different techniques to achieve the same effect. Although each has its pros and cons, you can see that it’s totally possible to slide in a color change on text. It’s a neat little effect that makes links feel a little more interactive.
Cancelling out one transform with the other does not exactly give a stable result in all browsers on all devices. Case in point: it produces an extremely visible 1px jitter in Firefox.
Only in the embedded codepen on my system. Opening the codepen in a new tab resolves this issue for me, which is likely why the author didn’t encounter it.
After seeing the mention of accessibility in Technique 4, I was wondering if accessibility would also be a concern for Techniques 2 and 3. I believe that screen readers would also read duplicate content from the pseudo elements as well as the actual link text.
It looks like there is a way to set alternative text for the “content” css attribute:
https://stackoverflow.com/questions/47438877/how-to-hide-css-generated-content-from-screen-readers-without-html-markup/47451397#47451397
Pro level hacks Katherine, Thanks!!
I made a Svelte component out of the last example for max
https://svelte.dev/repl/c6aefed780b949139dea95a323d8e48f?version=3.19.2
Haha. Might use it in a project though.
As simple as cool. Thanks a lot for these little tricks.
Try This Also: