Tag: Hover

A Fancy Hover Effect For Your Avatar

Do you know that kind of effect where someone’s head is poking through a circle or hole? The famous Porky Pig animation where he waves goodbye while popping out of a series of red rings is the perfect example, and Kilian Valkhof actually re-created that here on CSS-Tricks a while back.

I have a similar idea but tackled a different way and with a sprinkle of animation. I think it’s pretty practical and makes for a neat hover effect you can use on something like your own avatar.

See that? We’re going to make a scaling animation where the avatar seems to pop right out of the circle it’s in. Cool, right? Don’t look at the code and let’s build this animation together step-by-step.

The HTML: Just one element

If you haven’t checked the code of the demo and you are wondering how many divs this’ll take, then stop right there, because our markup is nothing but a single image element:

<img src="" alt="">

Yes, a single element! The challenging part of this exercise is using the smallest amount of code possible. If you have been following me for a while, you should be used to this. I try hard to find CSS solutions that can be achieved with the smallest, most maintainable code possible.

I wrote a series of articles here on CSS-Tricks where I explore different hover effects using the same HTML markup containing a single element. I go into detail on gradients, masking, clipping, outlines, and even layout techniques. I highly recommend checking those out because I will re-use many of the tricks in this post.

An image file that’s square with a transparent background will work best for what we’re doing. Here’s the one I’m using if you want start with that.

Designed by Cang

I’m hoping to see lots of examples of this as possible using real images — so please share your final result in the comments when you’re done so we can build a collection!

Before jumping into CSS, let’s first dissect the effect. The image gets bigger on hover, so we’ll for sure use transform: scale() in there. There’s a circle behind the avatar, and a radial gradient should do the trick. Finally, we need a way to create a border at the bottom of the circle that creates the appearance of the avatar behind the circle.

Let’s get to work!

The scale effect

Let’s start by adding the transform:

img {   width: 280px;   aspect-ratio: 1;   cursor: pointer;   transition: .5s; } img:hover {   transform: scale(1.35); }

Nothing complicated yet, right? Let’s move on.

The circle

We said that the background would be a radial gradient. That’s perfect because we can create hard stops between the colors of a radial gradient, which make it look like we’re drawing a circle with solid lines.

img {   --b: 5px; /* border width */    width: 280px;   aspect-ratio: 1;   background:     radial-gradient(       circle closest-side,       #ECD078 calc(99% - var(--b)),       #C02942 calc(100% - var(--b)) 99%,       #0000     );   cursor: pointer;   transition: .5s; } img:hover {   transform: scale(1.35); }

Note the CSS variable, --b, I’m using there. It represents the thickness of the “border” which is really just being used to define the hard color stops for the red part of the radial gradient.

The next step is to play with the gradient size on hover. The circle needs to keep its size as the image grows. Since we are applying a scale() transformation, we actually need to decrease the size of the circle because it otherwise scales up with the avatar. So, while the image scales up, we need the gradient to scale down.

Let’s start by defining a CSS variable, --f, that defines the “scale factor”, and use it to set the size of the circle. I’m using 1 as the default value, as in that’s the initial scale for the image and the circle that we transform from.

Here is a demo to illustrate the trick. Hover to see what is happening behind the scenes:

I added a third color to the radial-gradient to better identify the area of the gradient on hover:

radial-gradient(   circle closest-side,   #ECD078 calc(99% - var(--b)),   #C02942 calc(100% - var(--b)) 99%,   lightblue );

Now we have to position our background at the center of the circle and make sure it takes up the full height. I like to declare everything directly on the background shorthand property, so we can add our background positioning and make sure it doesn’t repeat by tacking on those values right after the radial-gradient():

background: radial-gradient() 50% / calc(100% / var(--f)) 100% no-repeat;

The background is placed at the center (50%), has a width equal to calc(100%/var(--f)), and has a height equal to 100%.

Nothing scales when --f is equal to 1 — again, our initial scale. Meanwhile, the gradient takes up the full width of the container. When we increase --f, the element’s size grows — thanks to the scale() transform — and the gradient’s size decreases.

Here’s what we get when we apply all of this to our demo:

We’re getting closer! We have the overflow effect at the top, but we still need to hide the bottom part of the image, so it looks like it is popping out of the circle rather than sitting in front of it. That’s the tricky part of this whole thing and is what we’re going to do next.

The bottom border

I first tried tackling this with the border-bottom property, but I was unable to find a way to match the size of the border to the size to the circle. Here’s the best I could get and you can immediately see it’s wrong:

The actual solution is to use the outline property. Yes, outline, not border. In a previous article, I show how outline is powerful and allows us to create cool hover effects. Combined with outline-offset, we have exactly what we need for our effect.

The idea is to set an outline on the image and adjust its offset to create the bottom border. The offset will depend on the scaling factor the same way the gradient size did.

Now we have our bottom “border” (actually an outline) combined with the “border” created by the gradient to create a full circle. We still need to hide portions of the outline (from the top and the sides), which we’ll get to in a moment.

Here’s our code so far, including a couple more CSS variables you can use to configure the image size (--s) and the “border” color (--c):

img {   --s: 280px; /* image size */   --b: 5px; /* border thickness */   --c: #C02942; /* border color */   --f: 1; /* initial scale */    width: var(--s);   aspect-ratio: 1;   cursor: pointer;   border-radius: 0 0 999px 999px;   outline: var(--b) solid var(--c);   outline-offset: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b));   background:      radial-gradient(       circle closest-side,       #ECD078 calc(99% - var(--b)),       var(--c) calc(100% - var(--b)) 99%,       #0000     ) 50% / calc(100% / var(--f)) 100% no-repeat;   transform: scale(var(--f));   transition: .5s; } img:hover {   --f: 1.35; /* hover scale */ }

Since we need a circular bottom border, we added a border-radius on the bottom side, allowing the outline to match the curvature of the gradient.

The calculation used on outline-offset is a lot more straightforward than it looks. By default, outline is drawn outside of the element’s box. And in our case, we need it to overlap the element. More precisely, we need it to follow the circle created by the gradient.

Diagram of the background transition.

When we scale the element, we see the space between the circle and the edge. Let’s not forget that the idea is to keep the circle at the same size after the scale transformation runs, which leaves us with the space we will use to define the outline’s offset as illustrated in the above figure.

Let’s not forget that the second element is scaled, so our result is also scaled… which means we need to divide the result by f to get the real offset value:

Offset = ((f - 1) * S/2) / f = (1 - 1/f) * S/2

We add a negative sign since we need the outline to go from the outside to the inside:

Offset = (1/f - 1) * S/2

Here’s a quick demo that shows how the outline follows the gradient:

You may already see it, but we still need the bottom outline to overlap the circle rather than letting it bleed through it. We can do that by removing the border’s size from the offset:

outline-offset: calc((1 / var(--f) - 1) * var(--s) / 2) - var(--b));

Now we need to find how to remove the top part from the outline. In other words, we only want the bottom part of the image’s outline.

First, let’s add space at the top with padding to help avoid the overlap at the top:

img {   --s: 280px; /* image size */   --b: 5px;   /* border thickness */   --c: #C02942; /* border color */   --f: 1; /* initial scale */    width: var(--s);   aspect-ratio: 1;   padding-block-start: calc(var(--s)/5);   /* etc. */ } img:hover {   --f: 1.35; /* hover scale */ }

There is no particular logic to that top padding. The idea is to ensure the outline doesn’t touch the avatar’s head. I used the element’s size to define that space to always have the same proportion.

Note that I have added the content-box value to the background:

background:   radial-gradient(     circle closest-side,     #ECD078 calc(99% - var(--b)),     var(--c) calc(100% - var(--b)) 99%,     #0000   ) 50%/calc(100%/var(--f)) 100% no-repeat content-box;

We need this because we added padding and we only want the background set to the content box, so we must explicitly tell the background to stop there.

Adding CSS mask to the mix

We reached the last part! All we need to do is to hide some pieces, and we are done. For this, we will rely on the mask property and, of course, gradients.

Here is a figure to illustrate what we need to hide or what we need to show to be more accurate

Showing how the mask applies to the bottom portion of the circle.

The left image is what we currently have, and the right is what we want. The green part illustrates the mask we must apply to the original image to get the final result.

We can identify two parts of our mask:

  • A circular part at the bottom that has the same dimension and curvature as the radial gradient we used to create the circle behind the avatar
  • A rectangle at the top that covers the area inside the outline. Notice how the outline is outside the green area at the top — that’s the most important part, as it allows the outline to be cut so that only the bottom part is visible.

Here’s our final CSS:

img {   --s: 280px; /* image size */   --b: 5px; /* border thickness */   --c: #C02942; /* border color */   --f: 1; /* initial scale */    --_g: 50% / calc(100% / var(--f)) 100% no-repeat content-box;   --_o: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b));    width: var(--s);   aspect-ratio: 1;   padding-top: calc(var(--s)/5);   cursor: pointer;   border-radius: 0 0 999px 999px;   outline: var(--b) solid var(--c);   outline-offset: var(--_o);   background:      radial-gradient(       circle closest-side,       #ECD078 calc(99% - var(--b)),       var(--c) calc(100% - var(--b)) 99%,       #0000) var(--_g);   mask:     linear-gradient(#000 0 0) no-repeat     50% calc(-1 * var(--_o)) / calc(100% / var(--f) - 2 * var(--b)) 50%,     radial-gradient(       circle closest-side,       #000 99%,       #0000) var(--_g);   transform: scale(var(--f));   transition: .5s; } img:hover {   --f: 1.35; /* hover scale */ }

Let’s break down that mask property. For starters, notice that a similar radial-gradient() from the background property is in there. I created a new variable, --_g, for the common parts to make things less cluttered.

--_g: 50% / calc(100% / var(--f)) 100% no-repeat content-box;  mask:   radial-gradient(     circle closest-side,     #000 99%,     #0000) var(--_g);

Next, there’s a linear-gradient() in there as well:

--_g: 50% / calc(100% / var(--f)) 100% no-repeat content-box;  mask:   linear-gradient(#000 0 0) no-repeat     50% calc(-1 * var(--_o)) / calc(100% / var(--f) - 2 * var(--b)) 50%,   radial-gradient(     circle closest-side,     #000 99%,     #0000) var(--_g);

This creates the rectangle part of the mask. Its width is equal to the radial gradient’s width minus twice the border thickness:

calc(100% / var(--f) - 2 * var(--b))

The rectangle’s height is equal to half, 50%, of the element’s size.

We also need the linear gradient placed at the horizontal center (50%) and offset from the top by the same value as the outline’s offset. I created another CSS variable, --_o, for the offset we previously defined:

--_o: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b));

One of the confusing things here is that we need a negative offset for the outline (to move it from outside to inside) but a positive offset for the gradient (to move from top to bottom). So, if you’re wondering why we multiply the offset, --_o, by -1, well, now you know!

Here is a demo to illustrate the mask’s gradient configuration:

Hover the above and see how everything move together. The middle box illustrates the mask layer composed of two gradients. Imagine it as the visible part of the left image, and you get the final result on the right!

Wrapping up

Oof, we’re done! And not only did we wind up with a slick hover animation, but we did it all with a single HTML <img> element. Just that and less than 20 lines of CSS trickery!

Sure, we relied on some little tricks and math formulas to reach such a complex effect. But we knew exactly what to do since we identified the pieces we needed up-front.

Could we have simplified the CSS if we allowed ourselves more HTML? Absolutely. But we’re here to learn new CSS tricks! This was a good exercise to explore CSS gradients, masking, the outline property’s behavior, transformations, and a whole bunch more. If you felt lost at any point, then definitely check out my series that uses the same general concepts. It sometimes helps to see more examples and use cases to drive a point home.

I will leave you with one last demo that uses photos of popular CSS developers. Don’t forget to show me a demo with your own image so I can add it to the collection!


A Fancy Hover Effect For Your Avatar originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , ,

Animated Background Stripes That Transition on Hover

How often to do you reach for the CSS background-size property? If you’re like me — and probably lots of other front-end folks — then it’s usually when you background-size: cover an image to fill the space of an entire element.

Well, I was presented with an interesting challenge that required more advanced background sizing: background stripes that transition on hover. Check this out and hover it with your cursor:

There’s a lot more going on there than the size of the background, but that was the trick I needed to get the stripes to transition. I thought I’d show you how I arrived there, not only because I think it’s a really nice visual effect, but because it required me to get creative with gradients and blend modes that I think you might enjoy.

Let’s start with a very basic setup to keep things simple. I’m talking about a single <div> in the HTML that’s styled as a green square:

<div></div>
div {   width: 500px;   height: 500px;   background: palegreen; }
Perfect square with a pale green background color.

Setting up the background stripes

If your mind went straight to a CSS linear gradient when you saw those stripes, then we’re already on the same page. We can’t exactly do a repeating gradient in this case since we want the stripes to occupy uneven amounts of space and transition them, but we can create five stripes by chaining five backgrounds on top of our existing background color and placing them to the top-right of the container:

div {   width: 500px;   height: 500px;   background:      linear-gradient(black, black) top right,     linear-gradient(black, black) top 100px right,     linear-gradient(black, black) top 200px right,     linear-gradient(black, black) top 300px right,     linear-gradient(black, black) top 400px right,      palegreen; }

I made horizontal stripes, but we could also go vertical with the approach we’re covering here. And we can simplify this quite a bit with custom properties:

div {   --gt: linear-gradient(black, black);   --n: 100px;    width: 500px;   height: 500px;   background:      var(--gt) top right,     var(--gt) top var(--n) right,     var(--gt) top calc(var(--n) * 2) right,     var(--gt) top calc(var(--n) * 3) right,     var(--gt) top calc(var(--n) * 4) right,      palegreen; }

So, the --gt value is the gradient and --n is a constant we’re using to nudge the stripes downward so they are offset vertically. And you may have noticed that I haven’t set a true gradient, but rather solid black stripes in the linear-gradient() function — that’s intentional and we’ll get to why I did that in a bit.

One more thing we ought to do before moving on is prevent our backgrounds from repeating; otherwise, they’ll tile and fill the entire space:

div {   --gt: linear-gradient(black, black);   --n: 100px;    width: 500px;   height: 500px;   background:      var(--gt) top right,     var(--gt) top var(--n) right,     var(--gt) top calc(var(--n) * 2) right,     var(--gt) top calc(var(--n) * 3) right,     var(--gt) top calc(var(--n) * 4) right,      palegreen;   background-repeat: no-repeat; }

We could have set background-repeat in the background shorthand, but I decided to break it out here to keep things easy to read.

Offsetting the stripes

We technically have stripes, but it’s pretty tough to tell because there’s no spacing between them and they cover the entire container. It’s more like we have a solid black square.

This is where we get to use the background-size property. We want to set both the height and the width of the stripes and the property supports a two-value syntax that allows us to do exactly that. And, we can chain those sizes by comma separating them the same way we did on background.

Let’s start simple by setting the widths first. Using the single-value syntax for background-size sets the width and defaults the height to auto. I’m using totally arbitrary values here, so set the values to what works best for your design:

div {   --gt: linear-gradient(black, black);   --n: 100px;    width: 500px;   height: 500px;   background:      var(--gt) top right,     var(--gt) top var(--n) right,     var(--gt) top calc(var(--n) * 2) right,     var(--gt) top calc(var(--n) * 3) right,     var(--gt) top calc(var(--n) * 4) right,      palegreen;   background-repeat: no-repeat;   background-size: 60%, 90%, 70%, 40%, 10%; }

If you’re using the same values that I am, you’ll get this:

Doesn’t exactly look like we set the width for all the stripes, does it? That’s because of the auto height behavior of the single-value syntax. The second stripe is wider than the others below it, and it is covering them. We ought to set the heights so we can see our work. They should all be the same height and we can actually re-use our --n variable, again, to keep things simple:

div {   --gt: linear-gradient(black, black);   --n: 100px;    width: 500px;   height: 500px;   background:      var(--gt) top right,     var(--gt) top var(--n) right,     var(--gt) top calc(var(--n) * 2) right,     var(--gt) top calc(var(--n) * 3) right,     var(--gt) top calc(var(--n) * 4) right,      palegreen;     background-repeat: no-repeat;     background-size: 60% var(--n), 90% var(--n), 70% var(--n), 40% var(--n), 10% var(--n); // HIGHLIGHT 15 }

Ah, much better!

Adding gaps between the stripes

This is a totally optional step if your design doesn’t require gaps between the stripes, but mine did and it’s not overly complicated. We change the height of each stripe’s background-size a smidge, decreasing the value so they fall short of filling the full vertical space.

We can continue to use our --n variable, but subtract a small amount, say 5px, using calc() to get what we want.

background-size: 60% calc(var(--n) - 5px), 90% calc(var(--n) - 5px), 70% calc(var(--n) - 5px), 40% calc(var(--n) - 5px), 10% calc(var(--n) - 5px);

That’s a lot of repetition we can eliminate with another variable:

div {   --h: calc(var(--n) - 5px);   /* etc. */   background-size: 60% var(--h), 90% var(--h), 70% var(--h), 40% var(--h), 10% var(--h); }

Masking and blending

Now let’s swap the palegreen background color we’ve been using for visual purposes up to this point for white.

div {   /* etc. */   background:      var(--gt) top right,     var(--gt) top var(--n) right,     var(--gt) top calc(var(--n) * 2) right,     var(--gt) top calc(var(--n) * 3) right,     var(--gt) top calc(var(--n) * 4) right,      #fff;   /* etc. */ }

A black and white pattern like this is perfect for masking and blending. To do that, we’re first going to wrap our <div> in a new parent container and introduce a second <div> under it:

<section>   <div></div>   <div></div> </section>

We’re going to do a little CSS re-factoring here. Now that we have a new parent container, we can pass the fixed width and height properties we were using on our <div> over there:

section {   width: 500px;   height: 500px; } 

I’m also going to use CSS Grid to position the two <div> elements on top of one another. This is the same trick Temani Afif uses to create his super cool image galleries. The idea is that we place both divs over the full container using the grid-area property and align everything toward the center:

section {   display: grid;   align-items: center;   justify-items: center;   width: 500px;   height: 500px; }   section > div {   width: inherit;   height: inherit;   grid-area: 1 / 1; }

Now, check this out. The reason I used a solid gradient that goes from black to black earlier is to set us up for masking and blending the two <div> layers. This isn’t true masking in the sense that we’re calling the mask property, but the contrast between the layers controls what colors are visible. The area covered by white will remain white, and the area covered by black leaks through. MDN’s documentation on blend modes has a nice explanation of how this works.

To get that working, I’ll apply the real gradient we want to see on the first <div> while applying the style rules from our initial <div> on the new one, using the :nth-child() pseudo-selector:

div:nth-child(1) {    background: linear-gradient(to right, red, orange);  }  div:nth-child(2)  {   --gt: linear-gradient(black, black);   --n: 100px;   --h: calc(var(--n) - 5px);   background:      var(--gt) top right,     var(--gt) top var(--n) right,     var(--gt) top calc(var(--n) * 2) right,     var(--gt) top calc(var(--n) * 3) right,     var(--gt) top calc(var(--n) * 4) right,      white;   background-repeat: no-repeat;   background-size: 60% var(--h), 90% var(--h), 70% var(--h), 40% var(--h), 10% var(--h); }

If we stop here, we actually won’t see any visual difference from what we had before. That’s because we haven’t done the actual blending yet. So, let’s do that now using the screen blend mode:

div:nth-child(2)  {   /* etc. */   mix-blend-mode: screen; }

I used a beige background color in the demo I showed at the beginning of this article. That slightly darker sort of off-white coloring allows a little color to bleed through the rest of the background:

The hover effect

The last piece of this puzzle is the hover effect that widens the stripes to full width. First, let’s write out our selector for it. We want this to happen when the parent container (<section> in our case) is hovered. When it’s hovered, we’ll change the background size of the stripes contained in the second <div>:

/* When <section> is hovered, change the second div's styles */ section:hover > div:nth-child(2){   /* styles go here */ }

We’ll want to change the background-size of the stripes to the full width of the container while maintaining the same height:

section:hover > div:nth-child(2){   background-size: 100% var(--h); }

That “snaps” the background to full-width. If we add a little transition to this, then we see the stripes expand on hover:

section:hover > div:nth-child(2){   background-size: 100% var(--h);   transition: background-size 1s; }

Here’s that final demo once again:

I only added text in there to show what it might look like to use this in a different context. If you do the same, then it’s worth making sure there’s enough contrast between the text color and the colors used in the gradient to comply with WCAG guidelines. And while we’re touching briefly on accessibility, it’s worth considering user preferences for reduced motion when it comes to the hover effect.

That’s a wrap!

Pretty neat, right? I certainly think so. What I like about this, too, is that it’s pretty maintainable and customizable. For example, we can alter the height, colors, and direction of the stripes by changing a few values. You might even variablize a few more things in there — like the colors and widths — to make it even more configurable.

I’m really interested if you would have approached this a different way. If so, please share in the comments! It’d be neat to see how many variations we can collect.


Animated Background Stripes That Transition on Hover originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , ,
[Top]

Fancy Image Decorations: Masks and Advanced Hover Effects

Welcome to Part 2 of this three-part series! We are still decorating images without any extra elements and pseudo-elements. I hope you already took the time to digest Part 1 because we will continue working with a lot of gradients to create awesome visual effects. We are also going to introduce the CSS mask property for more complex decorations and hover effects.

Fancy Image Decorations series

  • Single Element Magic
  • Masks and Advanced Hover Effects (you are here!)
  • Outlines and Complex Animations (coming October 28 )

Let’s turn to the first example we’re working on together…

The Postage Stamp

Believe or not, all it takes to make postage stamp CSS effect is two gradients and a filter:

img {   --r: 10px; /* control the radius of the circles */   padding: calc(2 * var(--r));   filter: grayscale(.4);   background:      radial-gradient(var(--r),#0000 98%,#fff) round       calc(-1.5 * var(--r)) calc(-1.5 * var(--r)) / calc(3 * var(--r)) calc(3 * var(--r)),     linear-gradient(#fff 0 0) no-repeat       50% / calc(100% - 3 * var(--r)) calc(100% - 3 * var(--r)); }

As we saw in the previous article, the first step is to make space around the image with padding so we can draw a background gradient and see it there. Then we use a combination of radial-gradient() and linear-gradient() to cut those circles around the image.

Here is a step-by-step illustration that shows how the gradients are configured:

Note the use of the round value in the second step. It’s very important for the trick as it ensures the size of the gradient is adjusted to be perfectly aligned on all the sides, no matter what the image width or height is.

From the specification: The image is repeated as often as will fit within the background positioning area. If it doesn’t fit a whole number of times, it is rescaled so that it does.

The Rounded Frame

Let’s look at another image decoration that uses circles…

This example also uses a radial-gradient(), but this time I have created circles around the image instead of the cut-out effect. Notice that I am also using the round value again. The trickiest part here is the transparent gap between the frame and the image, which is where I reach for the CSS mask property:

img {   --s: 20px; /* size of the frame */   --g: 10px; /* the gap */   --c: #FA6900;     padding: calc(var(--g) + var(--s));   background:      radial-gradient(farthest-side, var(--c) 97%, #0000)        0 0 / calc(2 * var(--s)) calc(2 * var(--s)) round;   mask:     conic-gradient(from 90deg at calc(2 * var(--s)) calc(2 * var(--s)), #0000 25%, #000 0)       calc(-1 * var(--s)) calc(-1 * var(--s)),     linear-gradient(#000 0 0) content-box; }

Masking allows us to show the area of the image — thanks to the linear-gradient() in there — as well as 20px around each side of it — thanks to the conic-gradient(). The 20px is nothing but the variable --s that defines the size of the frame. In other words, we need to hide the gap.

Here’s what I mean:

The linear gradient is the blue part of the background while the conic gradient is the red part of the background. That transparent part between both gradients is what we cut from our element to create the illusion of an inner transparent border.

The Inner Transparent Border

For this one, we are not going to create a frame but rather try something different. We are going to create a transparent inner border inside our image. Probably not that useful in a real-world scenario, but it’s good practice with CSS masks.

Similar to the previous example, we are going to rely on two gradients: a linear-gradient() for the inner part, and a conic-gradient() for the outer part. We’ll leave a space between them to create the transparent border effect.

img {   --b: 5px;  /* the border thickness */   --d: 20px; /* the distance from the edge */    --_g: calc(100% - 2 * (var(--d) + var(--b)));   mask:     conic-gradient(from 90deg at var(--d) var(--d), #0000 25%, #000 0)       0 0 / calc(100% - var(--d)) calc(100% - var(--d)),     linear-gradient(#000 0 0) 50% / var(--_g) var(--_g) no-repeat; }
Detailing the parts of the image that correspond to CSS variables.

You may have noticed that the conic gradient of this example has a different syntax from the previous example. Both are supposed to create the same shape, so why are they different? It’s because we can reach the same result using different syntaxes. This may look confusing at first, but it’s a good feature. You are not obliged to find the solution to achieve a particular shape. You only need to find one solution that works for you out of the many possibilities out there.

Here are four ways to create the outer square using gradients:

There are even more ways to pull this off, but you get the point.

There is no Best™ approach. Personally, I try to find the one with the smallest and most optimized code. For me, any solution that requires fewer gradients, fewer calculations, and fewer repeated values is the most suitable. Sometimes I choose a more verbose syntax because it gives me more flexibility to change variables and modify things. It comes with experience and practice. The more you play with gradients, the more you know what syntax to use and when.

Let’s get back to our inner transparent border and dig into the hover effect. In case you didn’t notice, there is a cool hover effect that moves that transparent border using a font-size trick. The idea is to define the --d variable with a value of 1em. This variables controls the distance of the border from the edge. We can transform like this:

--_d: calc(var(--d) + var(--s) * 1em)

…giving us the following updated CSS:

img {   --b: 5px;  /* the border thickness */   --d: 20px; /* the distance from the edge */   --o: 15px; /* the offset on hover */   --s: 1;    /* the direction of the hover effect (+1 or -1)*/    --_d: calc(var(--d) + var(--s) * 1em);   --_g: calc(100% - 2 * (var(--_d) + var(--b)));   mask:     conic-gradient(from 90deg at var(--_d) var(--_d), #0000 25%, #000 0)      0 0 / calc(100% - var(--_d)) calc(100% - var(--_d)),     linear-gradient(#000 0 0) 50% / var(--_g) var(--_g) no-repeat;   font-size: 0;   transition: .35s; } img:hover {   font-size: var(--o); }

The font-size is initially equal to 0 ,so 1em is also equal to 0 and --_d is be equal to --d. On hover, though, the font-size is equal to a value defined by an --o variable that sets the border’s offset. This, in turn, updates the --_d variable, moving the border by the offset. Then I add another variable, --s, to control the sign that decides whether the border moves to the inside or the outside.

The font-size trick is really useful if we want to animate properties that are otherwise unanimatable. Custom properties defined with @property can solve this but support for it is still lacking at the time I’m writing this.

The Frame Reveal

We made the following reveal animation in the first part of this series:

We can take the same idea, but instead of a border with a solid color we will use a gradient like this:

If you compare both codes you will notice the following changes:

  1. I used the same gradient configuration from the first example inside the mask property. I simply moved the gradients from the background property to the mask property.
  2. I added a repeating-linear-gradient() to create the gradient border.

That’s it! I re-used most of the same code we already saw — with super small tweaks — and got another cool image decoration with a hover effect.

/* Solid color border */  img {   --c: #8A9B0F; /* the border color */   --b: 10px;   /* the border thickness*/   --g: 5px;  /* the gap on hover */    padding: calc(var(--g) + var(--b));   --_g: #0000 25%, var(--c) 0;   background:      conic-gradient(from 180deg at top var(--b) right var(--b), var(--_g))      var(--_i, 200%) 0 / 200% var(--_i, var(--b)) no-repeat,     conic-gradient(at bottom var(--b) left  var(--b), var(--_g))      0 var(--_i, 200%) / var(--_i, var(--b)) 200% no-repeat;   transition: .3s, background-position .3s .3s;   cursor: pointer; } img:hover {   --_i: 100%;   transition: .3s, background-size .3s .3s; }

/* Gradient color border */  img {   --b: 10px; /* the border thickness*/   --g: 5px;  /* the gap on hover */   background: repeating-linear-gradient(135deg, #F8CA00 0 10px, #E97F02 0 20px, #BD1550 0 30px);    padding: calc(var(--g) + var(--b));   --_g: #0000 25%, #000 0;   mask:      conic-gradient(from 180deg at top var(--b) right var(--b), var(--_g))      var(--_i, 200%) 0 / 200% var(--_i, var(--b)) no-repeat,     conic-gradient(at bottom var(--b) left  var(--b), var(--_g))      0 var(--_i, 200%) / var(--_i, var(--b)) 200% no-repeat,     linear-gradient(#000 0 0) content-box;   transition: .3s, mask-position .3s .3s;   cursor: pointer; } img:hover {   --_i: 100%;   transition: .3s, mask-size .3s .3s; } 

Let’s try another frame animation. This one is a bit tricky as it has a three-step animation:

The first step of the animation is to make the bottom edge bigger. For this, we adjust the background-size of a linear-gradient():

You are probably wondering why I am also adding the top edge. We need it for the third step. I always try to optimize the code I write, so I am using one gradient to cover both the top and bottom sides, but the top one is hidden and revealed later with a mask.

For the second step, we add a second gradient to show the left and right edges. But this time, we do it using background-position:

We can stop here as we already have a nice effect with two gradients but we are here to push the limits so let’s add a touch of mask to achieve the third step.

The trick is to make the top edge hidden until we show the bottom and the sides and then we update the mask-size (or mask-position) to show the top part. As I said previously, we can find a lot of gradient configurations to achieve the same effect.

Here is an illustration of the gradients I will be using:

I am using two conic gradients having a width equal to 200%. Both gradients cover the area leaving only the top part uncovered (that part will be invisible later). On hover, I slide both gradients to cover that part.

Here is a better illustration of one of the gradients to give you a better idea of what’s happening:

Now we put this inside the mask property and we are done! Here is the full code:

img {   --b: 6px;  /* the border thickness*/   --g: 10px; /* the gap */   --c: #0E8D94;    padding: calc(var(--b) + var(--g));   --_l: var(--c) var(--b), #0000 0 calc(100% - var(--b)), var(--c) 0;   background:     linear-gradient(var(--_l)) 50%/calc(100% - var(--_i,80%)) 100% no-repeat,     linear-gradient(90deg, var(--_l)) 50% var(--_i,-100%)/100% 200% no-repeat;     mask:     conic-gradient(at 50% var(--b),#0000 25%, #000 0) calc(50% + var(--_i, 50%)) / 200%,     conic-gradient(at 50% var(--b),#000 75%, #0000 0) calc(50% - var(--_i, 50%)) / 200%;   transition:      .3s calc(.6s - var(--_t,.6s)) mask-position,      .3s .3s background-position,     .3s var(--_t,.6s) background-size,     .4s transform;   cursor: pointer; } img:hover {   --_i: 0%;   --_t: 0s;   transform: scale(1.2); }

I have also introduced some variables to optimize the code, but you should be used to this right now.

What about a four-step animation? Yes, it’s possible!

No explanation for this because it’s your homework! Take all that you have learned in this article to dissect the code and try to articulate what it’s doing. The logic is similar to all the previous examples. The key is to isolate each gradient to understand each step of the animation. I kept the code un-optimized to make things a little easier to read. I do have an optimized version if you are interested, but you can also try to optimize the code yourself and compare it with my version for additional practice.

Wrapping up

That’s it for Part 2 of this three-part series on creative image decorations using only the <img> element. We now have a good handle on how gradients and masks can be combined to create awesome visual effects, and even animations — without reaching for extra elements or pseudo-elements. Yes, a single <img> tag is enough!

We have one more article in this series to go. Until then, here is a bonus demo with a cool hover effect where I use mask to assemble a broken image.

Fancy Image Decorations series

  • Single Element Magic
  • Masks and Advanced Hover Effects (you are here!)
  • Outlines and Complex Animations (coming October 28 )

Fancy Image Decorations: Masks and Advanced Hover Effects originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , , , ,
[Top]

CSS Checkerboard Background… But With Rounded Corners and Hover Styles

On one hand, creating simple checkered backgrounds with CSS is easy. On the other hand, though, unless we are one of the CSS-gradient-ninjas, we are kind of stuck with basic patterns.

At least that’s what I thought while staring at the checkered background on my screen and trying to round those corners of the squares just a little…until I remembered my favorite bullet point glyph — — and figured that if only I could place it over every intersection in the pattern, I’ll surely get the design I want.

Turns out it’s possible! Here’s the proof.

Let’s start with the basic pattern:

<div></div>
div {  background:    repeating-linear-gradient(     to right, transparent,      transparent 50px,      white 50px,      white 55px   ),   repeating-linear-gradient(     to bottom, transparent,       transparent 50px,      white 50px,      white 55px   ),   linear-gradient(45deg, pink, skyblue);   /* more styles */ }

What that gives us is a repeating background of squares that go from pink to blue with 5px white gaps between them. Each square is fifty pixels wide and transparent. This is created using repeating-linear-gradient, which creates a linear gradient image where the gradient repeats throughout the containing area.

In other words, the first gradient in that sequence creates white horizontal stripes and the second gradient creates white vertical stripes. Layered together, they form the checkered pattern, and the third gradient fills in the rest of the space.

Now we add the star glyph I mentioned earlier, on top of the background pattern. We can do that by including it on the same background property as the gradients while using an encoded SVG for the shape:

div {   background:      repeat left -17px top -22px/55px 55px     url("data:image/svg+xml,     <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 35px 35px'>       <foreignObject width='35px' height='35px'>         <div xmlns='http://www.w3.org/1999/xhtml' style='color: white; font-size: 35px'>✦</div>       </foreignObject>     </svg>"     ),      repeating-linear-gradient(       to right, transparent,       transparent 50px,       white 50px,       white 55px     ),     repeating-linear-gradient(       to bottom, transparent,       transparent 50px,       white 50px,       white 55px     ),     linear-gradient(45deg, pink, skyblue);   /* more style */ }

Let’s break that down. The first keyword, repeat, denotes that this is a repeating background image. Followed by that is the position and size of each repeating unit, respectively (left -17px top -22px/55px 55px). This offset position is based on the glyph and pattern’s size. You’ll see below how the glyph size is given. The offset is added to re-position the repeating glyph exactly over each intersection in the checkered pattern.

The SVG has an HTML <div> carrying the glyph. Notice that I declared a font-size on it. That ultimately determines the border radius of the squares in the checkerboard pattern — the bigger the glyph, the more rounded the squares. The unrolled SVG from the data URL looks like this:

<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 35px 35px'>   <foreignObject width='35px' height='35px'>     <div xmlns='http://www.w3.org/1999/xhtml' style='color:white;font-size:35px'>✦</div>   </foreignObject> </svg>

Now that a CSS pattern is established, let’s add a :hover effect where the glyph is removed and the white lines are made slightly translucent by using rgb() color values with alpha transparency.

div:hover {   background:     repeating-linear-gradient(       to right, transparent,       transparent 50px,       rgb(255 255 255 / 0.5) 50px,       rgb(255 255 255 / 0.5) 55px     ),     repeating-linear-gradient(       to bottom, transparent,       transparent 50px,       rgb(255 255 255 / 0.5) 50px,       rgb(255 255 255 / 0.5) 55px     ),   linear-gradient(45deg, pink, skyblue);   box-shadow: 10px 10px 20px pink; }

There we go! Now, not only do we have our rounded corners, but we also have more control control over the pattern for effects like this:

Again, this whole exercise was an attempt to get a grid of squares in a checkerboard pattern that supports rounded corners, a background gradient that serves as an overlay across the pattern, and interactive styles. I think this accomplishes the task quite well, but I’m also interested in how you might’ve approached it. Let me know in the comments!


CSS Checkerboard Background… But With Rounded Corners and Hover Styles originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , , ,
[Top]

Cool CSS Hover Effects That Use Background Clipping, Masks, and 3D

We’ve walked through a series of posts now about interesting approaches to CSS hover effects. We started with a bunch of examples that use CSS background properties, then moved on to the text-shadow property where we technically didn’t use any shadows. We also combined them with CSS variables and calc() to optimize the code and make it easy to manage.

In this article, we will build off those two articles to create even more complex CSS hover animations. We’re talking about background clipping, CSS masks, and even getting our feet wet with 3D perspectives. In other words, we are going to explore advanced techniques this time around and push the limits of what CSS can do with hover effects!

Cool Hover Effects series:

  1. Cool Hover Effects That Use Background Properties
  2. Cool Hover Effects That Use CSS Text Shadow
  3. Cool Hover Effects That Use Background Clipping, Masks, and 3D (you are here!)

Here’s just a taste of what we’re making:

Hover effects using background-clip

Let’s talk about background-clip. This CSS property accepts a text keyword value that allows us to apply gradients to the text of an element instead of the actual background.

So, for example, we can change the color of the text on hover as we would using the color property, but this way we animate the color change:

All I did was add background-clip: text to the element and transition the background-position. Doesn’t have to be more complicated than that!

But we can do better if we combine multiple gradients with different background clipping values.

In that example, I use two different gradients and two values with background-clip. The first background gradient is clipped to the text (thanks to the text value) to set the color on hover, while the second background gradient creates the bottom underline (thanks to the padding-box value). Everything else is straight up copied from the work we did in the first article of this series.

How about a hover effect where the bar slides from top to bottom in a way that looks like the text is scanned, then colored in:

This time I changed the size of the first gradient to create the line. Then I slide it with the other gradient that update the text color to create the illusion! You can visualize what’s happening in this pen:

We’ve only scratched the surface of what we can do with our background-clipping powers! However, this technique is likely something you’d want to avoid using in production, as Firefox is known to have a lot of reported bugs related to background-clip. Safari has support issues as well. That leaves only Chrome with solid support for this stuff, so maybe have it open as we continue.

Let’s move on to another hover effect using background-clip:

You’re probably thinking this one looks super easy compared to what we’ve just covered — and you are right, there’s nothing fancy here. All I am doing is sliding one gradient while increasing the size of another one.

But we’re here to look at advanced hover effects, right? Let’s change it up a bit so the animation is different when the mouse cursor leaves the element. Same hover effect, but a different ending to the animation:

Cool right? let’s dissect the code:

.hover {   --c: #1095c1; /* the color */    color: #0000;   background:      linear-gradient(90deg, #fff 50%, var(--c) 0) calc(100% - var(--_p, 0%)) / 200%,      linear-gradient(var(--c) 0 0) 0% 100% / var(--_p, 0%) no-repeat,     var(--_c, #0000);   -webkit-background-clip: text, padding-box, padding-box;           background-clip: text, padding-box, padding-box;   transition: 0s, color .5s, background-color .5s; } .hover:hover {   color: #fff;   --_c: var(--c);   --_p: 100%;   transition: 0.5s, color 0s .5s, background-color 0s .5s; }

We have three background layers — two gradients and the background-color defined using --_c variable which is initially set to transparent (#0000). On hover, we change the color to white and the --_c variable to the main color (--c).

Here’s what is happening on that transition: First, we apply a transition to everything but we delay the color and background-color by 0.5s to create the sliding effect. Right after that, we change the color and the background-color. You might notice no visual changes because the text is already white (thanks to the first gradient) and the background is already set to the main color (thanks to the second gradient).

Then, on mouse out, we apply an instant change to everything (notice the 0s delay), except for the color and background-color that have a transition. This means that we put all the gradients back to their initial states. Again, you will probably see no visual changes because the text color and background-color already changed on hover.

Lastly, we apply the fading to color and a background-color to create the mouse-out part of the animation. I know, it may be tricky to grasp but you can better visualize the trick by using different colors:

Hover the above a lot of times and you will see the properties that are animating on hover and the ones animating on mouse out. You can then understand how we reached two different animations for the same hover effect.

Let’s not forget the DRY switching technique we used in the previous articles of this series to help reduce the amount of code by using only one variable for the switch:

.hover {   --c: 16 149 193; /* the color using the RGB format */    color: rgb(255 255 255 / var(--_i, 0));   background:     /* Gradient #1 */     linear-gradient(90deg, #fff 50%, rgb(var(--c)) 0) calc(100% - var(--_i, 0) * 100%) / 200%,     /* Gradient #2 */     linear-gradient(rgb(var(--c)) 0 0) 0% 100% / calc(var(--_i, 0) * 100%) no-repeat,     /* Background Color */     rgb(var(--c)/ var(--_i, 0));   -webkit-background-clip: text, padding-box, padding-box;           background-clip: text, padding-box, padding-box;   --_t: calc(var(--_i,0)*.5s);   transition:      var(--_t),     color calc(.5s - var(--_t)) var(--_t),     background-color calc(.5s - var(--_t)) var(--_t); } .hover:hover {   --_i: 1; }

If you’re wondering why I reached for the RGB syntax for the main color, it’s because I needed to play with the alpha transparency. I am also using the variable --_t to reduce a redundant calculation used in the transition property.

Before we move to the next part here are more examples of hover effects I did a while ago that rely on background-clip. It would be too long to detail each one but with what we have learned so far you can easily understand the code. It can be a good inspiration to try some of them alone without looking at the code.

I know, I know. These are crazy and uncommon hover effects and I realize they are too much in most situations. But this is how to practice and learn CSS. Remember, we pushing the limits of CSS hover effects. The hover effect may be a novelty, but we’re learning new techniques along the way that can most certainly be used for other things.

Hover effects using CSS mask

Guess what? The CSS mask property uses gradients the same way the background property does, so you will see that what we’re making next is pretty straightforward.

Let’s start by building a fancy underline.

I’m using background to create a zig-zag bottom border in that demo. If I wanted to apply an animation to that underline, it would be tedious to do it using background properties alone.

Enter CSS mask.

The code may look strange but the logic is still the same as we did with all the previous background animations. The mask is composed of two gradients. The first gradient is defined with an opaque color that covers the content area (thanks to the content-box value). That first gradient makes the text visible and hides the bottom zig-zag border. content-box is the mask-clip value which behaves the same as background-clip

linear-gradient(#000 0 0) content-box

The second gradient will cover the whole area (thanks to padding-box). This one has a width that’s defined using the --_p variable, and it will be placed on the left side of the element.

linear-gradient(#000 0 0) 0 / var(--_p, 0%) padding-box

Now, all we have to do is to change the value of --_p on hover to create a sliding effect for the second gradient and reveal the underline.

.hover:hover {   --_p: 100%;   color: var(--c); }

The following demo uses with the mask layers as backgrounds to better see the trick taking place. Imagine that the green and red parts are the visible parts of the element while everything else is transparent. That’s what the mask will do if we use the same gradients with it.

With such a trick, we can easily create a lot of variation by simply using a different gradient configuration with the mask property:

Each example in that demo uses a slightly different gradient configuration for the mask. Notice, too, the separation in the code between the background configuration and the mask configuration. They can be managed and maintained independently.

Let’s change the background configuration by replacing the zig-zag underline with a wavy underline instead:

Another collection of hover effects! I kept all the mask configurations and changed the background to create a different shape. Now, you can understand how I was able to reach 400 hover effects without pseudo-elements — and we can still have more!

Like, why not something like this:

Here’s a challenge for you: The border in that last demo is a gradient using the mask property to reveal it. Can you figure out the logic behind the animation? It may look complex at first glance, but it’s super similar to the logic we’ve looked at for most of the other hover effects that rely on gradients. Post your explanation in the comments!

Hover effects in 3D

You may think it’s impossible to create a 3D effect with a single element (and without resorting to pseudo-elements!) but CSS has a way to make it happen.

What you’re seeing there isn’t a real 3D effect, but rather a perfect illusion of 3D in the 2D space that combines the CSS background, clip-path, and transform properties.

Breakdown of the CSS hover effect in four stages.
The trick may look like we’re interacting with a 3D element, but we’re merely using 2D tactics to draw a 3D box

The first thing we do is to define our variables:

--c: #1095c1; /* color */ --b: .1em; /* border length */ --d: 20px; /* cube depth */

Then we create a transparent border with widths that use the above variables:

--_s: calc(var(--d) + var(--b)); color: var(--c); border: solid #0000; /* fourth value sets the color's alpha */ border-width: var(--b) var(--b) var(--_s) var(--_s);

The top and right sides of the element both need to equal the --b value while the bottom and left sides need to equal to the sum of --b and --d (which is the --_s variable).

For the second part of the trick, we need to define one gradient that covers all the border areas we previously defined. A conic-gradient will work for that:

background: conic-gradient(   at left var(--_s) bottom var(--_s),   #0000 90deg,var(--c) 0  )   0 100% / calc(100% - var(--b)) calc(100% - var(--b)) border-box;
Diagram of the sizing used for the hover effect.

We add another gradient for the third part of the trick. This one will use two semi-transparent white color values that overlap the first previous gradient to create different shades of the main color, giving us the illusion of shading and depth.

conic-gradient(   at left var(--d) bottom var(--d),   #0000 90deg,   rgb(255 255 255 / 0.3) 0 225deg,   rgb(255 255 255 / 0.6) 0 ) border-box
Showing the angles used to create the hover effect.

The last step is to apply a CSS clip-path to cut the corners for that long shadow sorta feel:

clip-path: polygon(   0% var(--d),    var(--d) 0%,    100% 0%,    100% calc(100% - var(--d)),    calc(100% - var(--d)) 100%,    0% 100% )
Showing the coordinate points of the three-dimensional cube used in the CSS hover effect.

That’s all! We just made a 3D rectangle with nothing but two gradients and a clip-path that we can easily adjust using CSS variables. Now, all we have to do is to animate it!

Notice the coordinates from the previous figure (indicated in red). Let’s update those to create the animation:

clip-path: polygon(   0% var(--d), /* reverses var(--d) 0% */   var(--d) 0%,    100% 0%,    100% calc(100% - var(--d)),    calc(100% - var(--d)) 100%, /* reverses 100% calc(100% - var(--d)) */    0% 100% /* reverses var(--d) calc(100% - var(--d)) */ )

The trick is to hide the bottom and left parts of the element so all that’s left is a rectangular element with no depth whatsoever.

This pen isolates the clip-path portion of the animation to see what it’s doing:

The final touch is to move the element in the opposite direction using translate — and the illusion is perfect! Here’s the effect using different custom property values for varying depths:

The second hover effect follows the same structure. All I did is to update a few values to create a top left movement instead of a top right one.

Combining effects!

The awesome thing about everything we’ve covered is that they all complement each other. Here is an example where I am adding the text-shadow effect from the second article in the series to the background animation technique from the first article while using the 3D trick we just covered:

The actual code might be confusing at first, but go ahead and dissect it a little further — you’ll notice that it’s merely a combination of those three different effects, pretty much smushed together.

Let me finish this article with a last hover effect where I am combining background, clip-path, and a dash of perspective to simulate another 3D effect:

I applied the same effect to images and the result was quite good for simulating 3D with a single element:

Want a closer look at how that last demo works? I wrote something up on it.

Wrapping up

Oof, we are done! I know, it’s a lot of tricky CSS but (1) we’re on the right website for that kind of thing, and (2) the goal is to push our understanding of different CSS properties to new levels by allowing them to interact with one another.

You may be asking what the next step is from here now that we’re closing out this little series of advanced CSS hover effects. I’d say the next step is to take all that we learned and apply them to other elements, like buttons, menu items, links, etc. We kept things rather simple as far as limiting our tricks to a heading element for that exact reason; the actual element doesn’t matter. Take the concepts and run with them to create, experiment with, and learn new things!


Cool CSS Hover Effects That Use Background Clipping, Masks, and 3D originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , , ,
[Top]

Cool Hover Effects That Use CSS Text Shadow

In my last article we saw how CSS background properties allow us to create cool hover effects. This time, we will focus on the CSS text-shadow property to explore even more interesting hovers. You are probably wondering how adding shadow to text can possibly give us a cool effect, but here’s the catch: we’re not actually going to make any shadows for these text hover effects.

text-shadow but no text shadows?

Let me clear the confusion by showing the hover effects we are going to build in the following demo:

Without looking at the code many of you will, intuitively, think that for each hover effect we are duplicating the text and then independently animating them. Now, if you check the code you will see that none of the text is actually duplicated in the HTML. And did you notice that there is no use of content: "text" in the CSS?

The text layers are completely made with text-shadow!

Hover effect #1

Let’s pick apart the CSS:

.hover-1 {   line-height: 1.2em;   color: #0000;   text-shadow:      0 0 #000,      0 1.2em #1095c1;   overflow: hidden;   transition: .3s; } .hover-1:hover {   text-shadow:      0 -1.2em #000,      0 0 #1095c1; }

The first thing to notice is that I am making the color of the actual text transparent (using #0000) in order to hide it. After that, I am using text-shadow to create two shadows where I am defining only two length values for each one. That means there’s no blur radius, making for a sharp, crisp shadow that effectively produces a copy of the text with the specified color.

That’s why I was able to claim in the introduction that there are no shadows in here. What we’re doing is less of a “classic” shadow than it is a simple way to duplicate the text.

Diagram of the start and end of the hover effect.

We have two text layers that we move on hover. If we hide the overflow, then the duplicated text is out of view and the movement makes it appear as though the actual text is being replaced by other text. This is the main trick that that makes all of the examples in this article work.

Let’s optimize our code. I am using the value 1.2em a lot to define the height and the offset of the shadows, making it an ideal candidate for a CSS custom property (which we’re calling --h):

.hover-1 {   --h: 1.2em;    line-height: var(--h);   color: #0000;   text-shadow:      0 0 #000,      0 var(--h) #1095c1;   overflow: hidden;   transition: .3s; } .hover-1:hover {   text-shadow:      0 calc(-1 * var(--h)) #000,      0 0 #1095c1; }

We can still go further and apply more calc()-ulations to streamline things to where we only use the text-shadow once. (We did the same in the previous article.)

.hover-1 {   --h: 1.2em;       line-height: var(--h);   color: #0000;   text-shadow:      0 calc(-1*var(--_t, 0em)) #000,      0 calc(var(--h) - var(--_t, 0em)) #1095c1;   overflow: hidden;   transition: .3s; } .hover-1:hover {   --_t: var(--h); }

In case you are wondering why I am adding an underscore to the --_t variable, it’s just a naming convention I am using to distinguish between the variables we use to control the effect that the user can update (like --h) and the internal variables that are only used for optimization purposes that we don’t need to change (like --_t ). In other words, the underscore is part of the variable name and has no special meaning.

We can also update the code to get the opposite effect where the duplicated text slides in from the top instead:

All we did is a small update to the text-shadow property — we didn’t touch anything else!

Hover effect #2

For this one, we will animate two properties: text-shadow and background. Concerning the text-shadow, we still have two layers like the previous example, but this time we will move only one of them while making the color of the other one transparent during the swap.

.hover-2 {   /* the height */   --h: 1.2em;    line-height: var(--h);   color: #0000;   text-shadow:      0 var(--_t, var(--h)) #fff,     0 0 var(--_c, #000);   transition: 0.3s; } .hover-2:hover {   --_t: 0;   --_c: #0000; }

On hover, we move the white text layer to the top while changing the color of the other one to transparent. To this, we add a background-size animation applied to a gradient:

And finally, we add overflow: hidden to keep the animation only visible inside the element’s boundaries:

.hover-2 {   /* the height */   --h: 1.2em;    line-height: var(--h);   color: #0000;   text-shadow:      0 var(--_t,var(--h)) #fff,     0 0 var(--_c, #000);   background:      linear-gradient(#1095c1 0 0)      bottom/100% var(--_d, 0) no-repeat;   overflow: hidden;   transition: 0.3s; } .hover-2:hover {   --_d: 100%;   --_t: 0;   --_c: #0000; }

What we’ve done here is combine the CSS text-shadow and background properties to create a cool hover effect. Plus, we were able to use CSS variables to optimize the code.

If the background syntax looks strange to you, I highly recommend reading my previous article. The next hover effect also relies on an animation I detailed in that article. Unless you are comfortable with CSS background trickery, I’d suggest reading that article before continuing this one for more context.

In the previous article, you show us how to use only one variable to create the hover effect — is it possible to do that here?

Yes, absolutely! We can indeed use that same DRY switching technique so that we’re only working with a single CSS custom property that merely switches values on hover:

.hover-2 {   /* the height */   --h: 1.2em;    line-height: var(--h);   color: #0000;   text-shadow:      0 var(--_i, var(--h)) #fff,     0 0 rgb(0 0 0 / calc(var(--_i, 1) * 100%) );   background:      linear-gradient(#1095c1 0 0)      bottom/100% calc(100% - var(--_i, 1) * 100%) no-repeat;   overflow: hidden;   transition: 0.3s; } .hover-2:hover {   --_i: 0; }

Hover effect #3

This hover effect is nothing but a combination of two effects we’ve already made: the second hover effect of the previous article and the first hover effect in this article.

.hover-3 {   /* the color  */   --c: #1095c1;   /* the height */   --h: 1.2em;    /* The first hover effect in this article */   line-height: var(--h);     color: #0000;   overflow: hidden;   text-shadow:      0 calc(-1 * var(--_t, 0em)) var(--c),      0 calc(var(--h) - var(--_t, 0em)) #fff;   /* The second hover effect from the previous article */   background:      linear-gradient(var(--c) 0 0) no-repeat      calc(200% - var(--_p, 0%)) 100% / 200% var(--_p, .08em);   transition: .3s var(--_s, 0s), background-position .3s calc(.3s - var(--_s, 0s)); } .hover-3:hover {   --_t: var(--h);   --_p: 100%;   --_s: .3s }

All I did was copy and paste the effects from those other examples and make minor adjustments to the variable names. They make for a neat hover effect when they’re combined! At first glance, such an effect may look complex and difficult but, in the end, it’s merely two relatively easy effects made into one.

Optimizing the code with the DRY switching variable technique should also be an easy task if we consider the previous optimizations we’ve already done:

.hover-3 {   /* the color  */   --c: #1095c1;   /* the height */   --h: 1.2em;    line-height: var(--h);     color: #0000;   overflow: hidden;   text-shadow:      0 calc(-1 * var(--h) * var(--_i, 0)) var(--c),      0 calc(var(--h) * (1 - var(--_i, 0))) #fff;   background:      linear-gradient(var(--c) 0 0) no-repeat     calc(200% - var(--_i, 0) * 100%) 100% / 200% calc(100% * var(--_i, 0) + .08em);   transition: .3s calc(var(--_i, 0) * .3s), background-position .3s calc(.3s - calc(var(--_i, 0) * .3s)); } .hover-3:hover {   --_i: 1; }

Hover effect #4

This hover effect is an improvement of the second one. First, let’s introduce a clip-path animation to reveal one of the text layers before it moves:

Here’s another illustration to better understand what is happening:

Diagram of the start and end of the text hover.

Initially, we use inset(0 0 0 0) which is similar to overflow: hidden in that all we see is the actual text. On hover, we update the the third value (which represent the bottom offset) using a negative value equal to the height to reveal the text layer placed at the bottom.

From there, we can add this to the second hover effect we made in this article, and this is what we get:

We are getting closer! Note that we need to first run the clip-path animation and then everything else. For this reason, we can add a delay to all of the properties on hover, except clip-path:

transition: 0.4s 0.4s, clip-path 0.4s;

And on mouse out, we do the opposite:

transition: 0.4s, clip-path 0.4s 0.4s;

The final touch is to add a box-shadow to create the sliding effect of the blue rectangle. Unfortunately, background is unable to produce the effect since backgrounds are clipped to the content area by default. Meanwhile, box-shadow can go outside the content area.

.hover-4 {   /* the color  */   --c: #1095c1;   /* the height */   --h: 1.2em;      line-height: var(--h);   color: #0000;   text-shadow:      0 var(--_t, var(--h)) #fff,     0 0 var(--_c, #000);   box-shadow: 0 var(--_t, var(--h)) var(--c);   clip-path: inset(0 0 0 0);   background: linear-gradient(var(--c) 0 0) 0 var(--_t, var(--h)) no-repeat;   transition: 0.4s, clip-path 0.4s 0.4s; } .hover-4:hover {   --_t: 0;   --_c: #0000;   clip-path: inset(0 0 calc(-1 * var(--h)) 0);   transition: 0.4s 0.4s, clip-path 0.4s; }

If you look closely at the box-shadow, you will see it has the same values as the white text layer inside text-shadow. This is logical since both need to move the same way. Both will slide to the top. Then the box-shadow goes behind the element while text-shadow winds up on the top.

Here is a demo with some modified values to visualize how the layers move:

Wait, The background syntax is a bit different from the one used in the second hover effect!

Good catch! Yes, we are using a different technique with background that produces the same effect. Instead of animating the size from 0% to 100%, we are animating the position.

If we don’t specify a size on our gradient, then it take up the full width and height by default. Since we know the height of our element (--h) we can create a sliding effect by updating the position from 0 var(--h) to 0 0.

.hover-4 {   /* ... */   background: linear-gradient(var(--c) 0 0) 0 var(--_t, var(--h)) no-repeat; } .hover-4:hover {   --_t: 0; }

We could have used the background-size animation to get the same effect, but we just added another trick to our list!

In the demos, you also used inset(0 0 1px 0)… why?

I sometimes add or remove a few pixels or percentages here and there to refine anything that looks off. In this case, a bad line was appearing at the bottom and adding 1px removed it.

What about the DRY switch variable optimization?

I am leaving this task for you! After those four hover effects and the previous article, you should be able to update the code so it only uses one variable. I’d love to see you attempt it in the comments!

Your turn!

Let me share one last hover effect which is another version of the previous one. Can you find out how it’s done without looking at the code? It’s a good exercise, so don’t cheat!

Wrapping up

We looked at a bunch of examples that show how one element and few lines of CSS are enough to create some pretty complex-looking hover effects on text elements — no pseudo elements needed! We were even able to combine techniques to achieve even more complex animations with a small amount of effort.

If you’re interested in going deeper than the four text-shadow hover effects in this article, check my collection of 500 hover effects where I am exploring all kinds of different techniques.


Cool Hover Effects That Use CSS Text Shadow originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , ,
[Top]

Cool Hover Effects That Use Background Properties

A while ago, Geoff wrote an article about a cool hover effect. The effect relies on a combination of CSS pseudo-elements, transforms, and transitions. A lot of comments have shown that the same effect can be done using background properties. Geoff mentioned that was his initial thought and that’s what I was thinking as well. I am not saying the pseudo-element he landed on is bad, but knowing different methods to achieve the same effect can only be a good thing.

In this post, we will re-work that hover effect, but also expand it into other types of hover effects that only use CSS background properties.

You can see the background properties at work in that demo, as well as how we can use custom properties and the calc() function to do even more. We are going to learn how to combine all of these so we are left with nicely optimized code!

Hover effect #1

Let’s start with the first effect which is the reproduction of the one detailed by Geoff in his article. The code used to achieve that effect is the following:

.hover-1 {   background: linear-gradient(#1095c1 0 0) var(--p, 0) / var(--p, 0) no-repeat;   transition: .4s, background-position 0s; } .hover-1:hover {   --p: 100%;   color: #fff; }

If we omit the color transition (which is optional), we only need three CSS declarations to achieve the effect. You are probably surprised how small the code is, but you will see how we got there.

First, let’s start with a simple background-size transition:

We are animating the size of a linear gradient from 0 100% to 100% 100%. That means the width is going from 0 to 100% while the background itself remains at full height. Nothing complex so far.

Let’s start our optimizations. We first transform our gradient to use the color only once:

background-image: linear-gradient(#1095c1 0 0);

The syntax might look a bit strange, but we are telling the browser that one color is applied to two color stops, and that’s enough to define a gradient in CSS. Both color stops are 0, so the browser automatically makes the last one 100% and fills our gradient with the same color. Shortcuts, FTW!

With background-size, we can omit the height because gradients are full height by default. We can do a transition from background-size: 0 to background-size: 100%.

.hover-1 {   background-image: linear-gradient(#1095c1 0 0);   background-size: 0;   background-repeat: no-repeat;   transition: .4s; } .hover-1:hover {   background-size: 100%; }

Let’s introduce a custom property to avoid the repetition of background-size:

.hover-1 {   background-image: linear-gradient(#1095c1 0 0);   background-size: var(--p, 0%);   background-repeat: no-repeat;   transition: .4s; } .hover-1:hover {   --p: 100%; }

We are not defining --p initially, so the fallback value (0% in our case) will be used. On hover, we define a value that replaces the fallback one ( 100%).

Now, let’s combine all the background properties using the shorthand version to get:

.hover-1 {   background: linear-gradient(#1095c1 0 0) left / var(--p, 0%) no-repeat;   transition: .4s; } .hover-1:hover {   --p: 100%; }

We are getting closer! Note that I have introduced a left value (for the background-position) which is mandatory when defining the size in the background shorthand. Plus, we need it anyway to achieve our hover effect.

We need to also update the position on hover. We can do that in two steps:

  1. Increase the size from the right on mouse hover.
  2. Decrease the size from the left on mouse out.

To do this, we need to update the background-position on hover as well:

We added two things to our code:

  • A background-position value of right on hover
  • A transition-duration of 0s on the background-position

This means that, on hover, we instantly change the background-position from left (see, we needed that value!) to right so the background’s size will increase from the right side. Then, when the mouse cursor leaves the link, the transition plays in reverse, from right to left, making it appear that we are decreasing the background’s size from the left side. Our hover effect is done!

But you said we only needed three declarations and there are four.

That’s true, nice catch. The left and right values can be changed to 0 0 and 100% 0, respectively; and since our gradient is already full height by default, we can get by using 0 and 100%.

.hover-1 {   background: linear-gradient(#1095c1 0 0) 0 / var(--p, 0%) no-repeat;   transition: .4s, background-position 0s; } .hover-1:hover {   --p: 100%;   background-position: 100%; }

See how background-position and --p are using the same values? Now we can reduce the code down to three declarations:

.hover-1 {   background: linear-gradient(#1095c1 0 0) var(--p, 0%) / var(--p,0%) no-repeat;   transition: .4s, background-position 0s; } .hover-1:hover {   --p: 100%; }

The custom property --p is defining both the background position and size. On hover, It will update both of them as well. This is a perfect use case showing how custom properties can help us reduce redundant code and avoid writing properties more than once. We define our setting using custom properties and we only update the latter on hover.

But the effect Geoff described is doing the opposite, starting from left and ending at right. How do we do that when it seems we cannot rely on the same variable?

We can still use one variable and update our code slightly to achieve the opposite effect. What we want is to go from 100% to 0% instead of 0% to 100%. We have a difference of 100% that we can express using calc(), like this:

.hover-1 {   background: linear-gradient(#1095c1 0 0) calc(100% - var(--p,0%)) / var(--p,0%) no-repeat;   transition: .4s, background-position 0s; } .hover-1:hover {   --p: 100%; }

--p will change from 0% to 100%, but the background’s position will change from 100% to 0%, thanks to calc().

We still have three declarations and one custom property, but a different effect.

Before we move to the next hover effect, I want to highlight something important that you have probably noticed. When dealing with custom properties, I am using 0% (with a unit) instead of a unit-less 0. The unit-less zero may work when the custom property is alone, but will fail inside calc() where we need to explicitly define the unit. I may need another article to explain this quirk but always remember to add the unit when dealing with custom properties. I have two answers on StackOverflow (here and here) that go into more detail.

Hover effect #2

We need a more complex transition for this effect. Let’s take a look at a step-by-step illustration to understand what is happening.

Diagram showing the hover effect in three pieces.
Initially, a fixed-height, full-width gradient is outside of view. Then we move the gradient to the right to cover the bottom side. Finally, we increase the size of the gradient from the fixed height to 100% to cover the whole element.

We first have a background-position transition followed by a background-size one. Let’s translate this into code:

.hover-2 {   background-image: linear-gradient(#1095c1 0 0);   background-size: 100% .08em; /* .08em is our fixed height; modify as needed. */   background-position: /* ??? */;   background-repeat: no-repeat;   transition: background-size .3s, background-position .3s .3s; } .hover-2:hover {   transition: background-size .3s .3s, background-position .3s;   background-size: 100% 100%;   background-position: /* ??? */; }

Note the use of two transition values. On hover, we need to first change the position and later the size, which is why we are adding a delay to the size. On mouse out, we do the opposite.

The question now is: what values do we use for background-position? We left those blank above. The background-size values are trivial, but the ones for background-position are not. And if we keep the actual configuration we’re unable to move our gradient.

Our gradient has a width equal to 100%, so we cannot use percentage values on background-position to move it.

Percentage values used with background-position are always a pain especially when you use them for the first time. Their behavior is non-intuitive but well defined and easy to understand if we get the logic behind it. I think it would take another article for a full explanation why it works this way, but here’s another “long” explanation I posted over at Stack Overflow. I recommend taking a few minutes to read that answer and you will thank me later!

The trick is to change the width to something different than 100%. Let’s use 200%. We’re not worried about the background exceeding the element because the overflow is hidden anyway.

.hover-2 {   background-image: linear-gradient(#1095c1 0 0);   background-size: 200% .08em;   background-position: 200% 100%;   background-repeat: no-repeat;   transition: background-size .3s, background-position .3s .3s; } .hover-2:hover {   transition: background-size .3s .3s, background-position .3s;   background-size: 200% 100%;   background-position: 100% 100%; }

And here’s what we get:

It’s time to optimize our code. If we take the ideas we learned from the first hover effect, we can use shorthand properties and write fewer declarations to make this work:

.hover-2 {   background:      linear-gradient(#1095c1 0 0) no-repeat     var(--p, 200%) 100% / 200% var(--p, .08em);   transition: .3s var(--t, 0s), background-position .3s calc(.3s - var(--t, 0s)); } .hover-2:hover {   --p: 100%;   --t: .3s; }

We add all the background properties together using the shorthand version then we use --p to express our values. The sizes change from .08em to 100% and the position from 200% to 100%

I am also using another variable --t , to optimize the transition property. On mouse hover we have it set to a .3s value, which gives us this:

transition: .3s .3s, background-position .3s 0s;

On mouse out, --t is undefined, so the fallback value will be used:

transition: .3s 0s, background-position .3s .3s;

Shouldn’t we have background-size in the transition?

That is indeed another optimization we can make. If we don’t specify any property it means “all” the properties, so the transition is defined for “all” the properties (including background-size and background-position). Then it’s defined again for background-position which is similar to defining it for background-size, then background-position.

“Similar” is different than saying something is the “same.” You will see a difference if you change more properties on hover, so the last optimization might be unsuitable in some cases.

Can we still optimize the code and use only one custom property?

Yes, we can! Ana Tudor shared a great article explaining how to create DRY switching where one custom property can update multiple properties. I won’t go into the details here, but our code can be revised like this:

.hover-2 {   background:      linear-gradient(#1095c1 0 0) no-repeat     calc(200% - var(--i, 0) * 100%) 100% / 200% calc(100% * var(--i, 0) + .08em);   transition: .3s calc(var(--i, 0) * .3s), background-position .3s calc(.3s - calc(var(--i, 0) * .3s)); } .hover-2:hover {   --i: 1; }

The --i custom property is initially undefined, so the fallback value, 0, is used. On hover though, we replace 0 with 1. You can do the math for both cases and get the values for each one. You can see that variable as a “switch” that update all our values at once on hover.

Again, we’re back to only three declarations for a pretty cool hover effect!

Hover effect #3

We are going to use two gradients instead of one for this effect. We will see that combining multiple gradients is another way to create fancy hover effects.

Here’s a diagram of what we’re doing:

We initially have two gradients that overflow the element so that they are out of view. Each one has a fixed height and toes up half of the element’s width. Then we slide them into view to make them visible. The first gradient is placed at the bottom-left and the second one at the top-right. Finally, we increase the height to cover the whole element.

Here’s how that looks in CSS:

.hover-3 {   background-image:     linear-gradient(#1095c1 0 0),     linear-gradient(#1095c1 0 0);   background-repeat: no-repeat;   background-size: 50% .08em;   background-position:     -100% 100%,     200% 0;   transition: background-size .3s, background-position .3s .3s; } .hover-3:hover {   background-size: 50% 100%;   background-position:     0 100%,     100% 0;     transition: background-size .3s .3s, background-position .3s; }

The code is almost the same as the other hover effects we’ve covered. The only difference is that we have two gradients with two different positions. The position values may look strange but, again, that’s related to how percentages work with the background-position property in CSS, so I highly recommend reading my Stack Overflow answer if you want to get into the gritty details.

Now let’s optimize! You get the idea by now — we’re using shorthand properties, custom properties, and calc() to tidy things up.

.hover-3 {   --c: no-repeat linear-gradient(#1095c1 0 0);   background:      var(--c) calc(-100% + var(--p, 0%)) 100% / 50% var(--p, .08em),     var(--c) calc( 200% - var(--p, 0%)) 0    / 50% var(--p, .08em);   transition: .3s var(--t, 0s), background-position .3s calc(.3s - var(--t, 0s)); } .hover-3:hover {   --p: 100%;   --t: 0.3s; }

I have added an extra custom property, --c, that defines the gradient since the same gradient is used in both places.

I am using 50.1% in that demo instead of 50% for the background size because it prevents a gap from showing between the gradients. I also added 1% to the positions for similar reasons.

Let’s do the second optimization by using the switch variable:

.hover-3 {   --c: no-repeat linear-gradient(#1095c1 0 0);   background:      var(--c) calc(-100% + var(--i, 0) * 100%) 100% / 50% calc(100% * var(--i, 0) + .08em),     var(--c) calc( 200% - var(--i, 0) * 100%) 0 / 50% calc(100% * var(--i, 0) + .08em);   transition: .3s calc(var(--i, 0) * .3s), background-position .3s calc(.3s - var(--i, 0) * .3s); } .hover-3:hover {   --i: 1; }

Are you started to see the patterns here? It’s not so much that the effects we’re making are difficult. It’s more the “final step” of code optimization. We start by writing verbose code with a lot of properties, then reduce it following simple rules (e.g. using shorthand, removing default values, avoiding redundant values, etc) to simplify things down as much as possible.

Hover effect #4

I will raise the difficulty level for this last effect, but you know enough from the other examples that I doubt you’ll have any issues with this one.

This hover effect relies on two conic gradients and more calculations.

Initially, we have both gradients with zero dimensions in Step 1. We increase the size of each one in Step 2. We keep increasing their widths until they fully cover the element, as shown in Step 3. After that, we slide them to the bottom to update their position. This is the “magic” part of the hover effect. Since both gradients will use the same coloration, changing their position in Step 4 will make no visual difference — but we will see a difference once we reduce the size on mouse out during Step 5.

If you compare Step 2 and Step 5, you can see that we have a different inclination. Let’s translate that into code:

.hover-4 {   background-image:     conic-gradient(/* ??? */),     conic-gradient(/* ??? */);   background-position:     0 0,     100% 0;   background-size: 0% 200%;   background-repeat: no-repeat;   transition: background-size .4s, background-position 0s; } .hover-4:hover {   background-size: /* ??? */ 200%;   background-position:     0 100%,     100% 100%; }

The positions are pretty clear. One gradient starts at top left (0 0) and ends at bottom left (0 100%) while the other starts at top right (100% 0) and ends at bottom right (100% 100%).

We’re using a transition on the background positions and sizes to reveal them. We only need a transition value for the background-size. And like before, background-position needs to change instantly, so we’re assigning a 0s value for the transition’s duration.

For the sizes, both gradient need to have 0 width and twice the element height (0% 200%). We will see later how their sizes change on hover. Let’s first define the gradient configuration.

The diagram below illustrates the configuration of each gradient:

Note that for the second gradient (indicated in green), we need to know the height to use it inside the conic-gradient we’re creating. For this reason, I am going to add a line-height that sets the element’s height and then try that same value for the conic gradient values we left out.

.hover-4 {   --c: #1095c1;   line-height: 1.2em;   background-image:     conic-gradient(from -135deg at 100%  50%, var(--c) 90deg, #0000 0),     conic-gradient(from -135deg at 1.2em 50%, #0000 90deg, var(--c) 0);   background-position:     0 0,     100% 0;   background-size: 0% 200%;   background-repeat: no-repeat;   transition: background-size .4s, background-position 0s; } .hover-4:hover {   background-size: /* ??? */ 200%;   background-position:     0 100%,     100% 100%; }

The last thing we have left is to figure out the background’s size. Intuitively, we may think that each gradient needs to take up half of the element’s width but that’s actually not enough.

We’re left with a large gap if we use 50% as the background-size value for both gradients.

We get a gap equal to the height, so we actually need to do is increase the size of each gradient by half the height on hover for them to cover the whole element.

.hover-4:hover {   background-size: calc(50% + .6em) 200%;   background-position:     0 100%,     100% 100%; }

Here’s what we get after optimizing them like the previous examples:

.hover-4 {   --c: #1095c1;   line-height: 1.2em;   background:     conic-gradient(from -135deg at 100%  50%, var(--c) 90deg, #0000 0)        0  var(--p, 0%) / var(--s, 0%) 200% no-repeat,     conic-gradient(from -135deg at 1.2em 50%, #0000 90deg, var(--c) 0)        100% var(--p, 0%) / var(--s, 0%) 200% no-repeat;   transition: .4s, background-position 0s; } .hover-4:hover {   --p: 100%;   --s: calc(50% + .6em); } 

What about the version with only one custom property?

I will leave that for you! After looking at four similar hover effects, you should be able to get the final optimization down to a single custom property. Share your work in the comment section! There’s no prize, but we may end up with different implementations and ideas that benefit everyone!

Before we end, let me share a version of that last hover effect that Ana Tudor cooked up. It’s an improvement! But note that it lacks Firefox supports due to a known bug. Still, it’s a great idea that shows how to combine gradients with blend modes to create even cooler hover effects.

Wrapping up

We made four super cool hover effects! And even though they are different effects, they all take the same approach of using CSS background properties, custom properties, and calc(). Different combinations allowed us to make different versions, all using the same techniques that leave us with clean, maintainable code.

If you want to get some ideas, I made a collection of 500 (yes, 500!) hover effects, 400 of which are done without pseudo-elements. The four we covered in this article are just the tip of the iceberg!


Cool Hover Effects That Use Background Properties originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , ,
[Top]

6 Creative Ideas for CSS Link Hover Effects

Creating CSS link hover effects can add a bit of flair to an otherwise bland webpage. If you’ve ever found yourself stumped trying to make a slick hover effect, then I have six CSS effects for you to take and use for your next project.

A default link hover effect above a styled link hover effect with a rainbow underline.

Let’s get right to it!

I know we’re talking about :hover and all, but it can sometimes (but maybe not always) be a good idea lump :focus in as well, as not all interactions are directly from a mouse, but perhaps a tap or keystroke.

This effect applies a box shadow to the inline link, altering the color of the link text in the process. We start with padding all around the link, then add a negative margin of the same value to prevent the padding from disrupting the text flow.

We will use box-shadow instead of the background property since it allows us to transition.

a {   box-shadow: inset 0 0 0 0 #54b3d6;   color: #54b3d6;   margin: 0 -.25rem;   padding: 0 .25rem;   transition: color .3s ease-in-out, box-shadow .3s ease-in-out; } a:hover {   box-shadow: inset 100px 0 0 0 #54b3d6;   color: white; }

Here’s a fun one where we swap the text of the link with some other text on hover. Hover over the text and the linked text slides out as new text slides in.

Easier to show than tell.

There’s quite a bit of trickery happening in this link hover effect. But the magic sauce is using a data-attribute to define the text that slides in and call it with the content property of the link’s ::after pseudo-element.

First off, the HTML markup:

<p>Hover <a href="#" data-replace="get a link"><span>get a link</span></a></p>

That’s a lot of inline markup, but you’re looking at a paragraph tag that contains a link and a span.

Let’s give link some base styles. We need to give it relative positioning to hold the pseudo-elements — which will be absolutely positioned — in place, make sure it’s displayed as inline-block to get box element styling affordances, and hide any overflow the pseudo-elements might cause.

a {   overflow: hidden;   position: relative;   display: inline-block; }

The ::before and ::after pseudo-elements should have some absolute positioning so they stack with the actual link. We’ll make sure they are set to the link’s full width with a zero offset in the left position, setting them up for some sliding action.

a::before, a::after {  content: '';   position: absolute;   width: 100%;   left: 0; }

The ::after pseudo-element gets the content from the link’s data-attribute that’s in the HTML markup:

a::after {   content: attr(data-replace); }

Now we can transform: translate3d() the ::after pseudo-element element to the right by 200%. We move it back into position on :hover. While we’re at it, we can give this a zero offset n the top direction. This’ll be important later when we use the ::before pseudo-element like an underline below the text.

a::after {   content: attr(data-replace);   top: 0;   transform-origin: 100% 50%;   transform: translate3d(200%, 0, 0); }  a:hover::after, a:focus::after {   transform: translate3d(0, 0, 0); }

We’re also going to transform: scale() the ::before pseudo-element so it’s hidden by default, then scale it back up on :hover. We’ll make it small, like 2px in height, and pin it to the bottom so it looks like an underline on the text that swaps in with ::after.

a::before {   background-color: #54b3d6;   height: 2px;   bottom: 0;   transform-origin: 100% 50%;   transform: scaleX(0); }  a:hover::before, a:focus::before {   transform-origin: 0% 50%;   transform: scaleX(1); }

The rest is all preference! We drop in a transition on the transform effects, some colors, and whatnot to get the full effect. Those values are totally up to you.

View full CSS
a {   overflow: hidden;   position: relative;   display: inline-block; }  a::before, a::after {  content: '';   position: absolute;   width: 100%;   left: 0; } a::before {   background-color: #54b3d6;   height: 2px;   bottom: 0;   transform-origin: 100% 50%;   transform: scaleX(0);   transition: transform .3s cubic-bezier(0.76, 0, 0.24, 1); } a::after {   content: attr(data-replace);   height: 100%;   top: 0;   transform-origin: 100% 50%;   transform: translate3d(200%, 0, 0);   transition: transform .3s cubic-bezier(0.76, 0, 0.24, 1);   color: #54b3d6; }  a:hover::before {   transform-origin: 0% 50%;   transform: scaleX(1); } a:hover::after {   transform: translate3d(0, 0, 0); }  a span {   display: inline-block;   transition: transform .3s cubic-bezier(0.76, 0, 0.24, 1); }  a:hover span {   transform: translate3d(-200%, 0, 0); }

This is a pretty popular effect I’ve seen used in quite a few places. The idea is that you use the link’s ::before pseudo-element as a thick underline that sits slightly behind the actual text of the link. Then, on hover, the pseudo-element expands to cover the whole thing.

OK, some base styles for the link. We want no text-decoration since ::before will act like one, then some relative positioning to hold ::before in place when we give that absolute positioning.

a {   text-decoration: none;   position: relative; }

Now let’s set up ::before by making it something like 8px tall so it looks like a thick underline. We’ll also give it absolute positioning so we have control to make it the full width of the actual link while offsetting it so it’s at the left and is just a smidge off the bottom so it looks like it’s subtly highlighting the link. May as well give it z-index: -1 so we’re assured it sits behind the link.

a::before {   content: '';   background-color: hsla(196, 61%, 58%, .75);   position: absolute;   left: 0;   bottom: 3px;   width: 100%;   height: 8px;   z-index: -1; }

Nice, nice. Let’s make it appear as though ::before is growing when the link is hovered. All we need is to change the height from 3px to 100%. Notice that I’m also dropping the bottom offset back to zero so the background covers more space when it grows.

a:hover::before {   bottom: 0;   height: 100%; }

Now for slight transition on those changes:

a::before {   content: '';   background-color: hsla(196, 61%, 58%, .75);   position: absolute;   left: 0;   bottom: 3px;   width: 100%;   height: 8px;   z-index: -1;   transition: all .3s ease-in-out; }
View full CSS
a {   text-decoration: none;   color: #18272F;   font-weight: 700;   position: relative; }  a::before {   content: '';   background-color: hsla(196, 61%, 58%, .75);   position: absolute;   left: 0;   bottom: 3px;   width: 100%;   height: 8px;   z-index: -1;   transition: all .3s ease-in-out; }  a:hover::before {   bottom: 0;   height: 100%; }

I personally like using this effect for links in a navigation. The link starts in one color without an underline. Then, on hover, a new color slides in from the right while an underline slides in from the left.

Neat, right? There’s a lot of motion happening in there, so you might consider the accessibility implications and wrap it all in a prefers-reduced-motion query to replace it with something more subtle for those with motion sensitivities.

Here’s how it works. We give the link a linear background gradient with a hard stop between two colors at the halfway mark.

a {   background-image: linear-gradient(     to right,     #54b3d6,     #54b3d6 50%,     #000 50%   ); }

We make the background double the link’s width, or 200%, and position it all the way over to the left. That way, it’s like only one of the gradients two colors is showing.

a {   background-image: linear-gradient(     to right,     #54b3d6,     #54b3d6 50%,     #000 50%   );   background-size: 200% 100%;   background-position: -100%; }

The magic happens when we reach for a couple of non-standard -webkit-prefixed properties. One strips the color out of the text to make it transparent. The other clips the background gradient to the text so it appears the text is actually the color of the background.

a {   background-image: linear-gradient(     to right,     #54b3d6,     #54b3d6 50%,     #000 50%   );   background-size: 200% 100%;   background-position: -100%;   -webkit-background-clip: text;   -webkit-text-fill-color: transparent; }

Still with me? Now let’s make the link’s faux underline by putting ::before to use. We’ll give it the same color we gave the on the hidden portion of the link’s background gradient and position it under the actual link so it looks like a proper text-decoration: underline.

a:before {   content: '';   background: #54b3d6;   display: block;   position: absolute;   bottom: -3px;   left: 0;   width: 0;   height: 3px; }

On hover, we slide ::before into place, coming in from the left:

a:hover {  background-position: 0; }

Now, this is a little tricky. On hover, we make the link’s ::before pseudo-element 100% of the link’s width. If we were to apply this directly to the link’s hover, we’d make the link itself full-width, which moves it around the screen. Yikes!

a:hover::before {   width: 100%; }

Add a little transition to smooth things out:

a {   background-image: linear-gradient(     to right,     #54b3d6,     #54b3d6 50%,     #000 50%   );   background-size: 200% 100%;   background-position: -100%;   -webkit-background-clip: text;   -webkit-text-fill-color: transparent;   transition: all 0.3s ease-in-out; }
View full CSS
a {   background-image: linear-gradient(     to right,     #54b3d6,     #54b3d6 50%,     #000 50%   );   background-size: 200% 100%;   background-position: -100%;   display: inline-block;   padding: 5px 0;   position: relative;   -webkit-background-clip: text;   -webkit-text-fill-color: transparent;   transition: all 0.3s ease-in-out; }  a:before {   content: '';   background: #54b3d6;   display: block;   position: absolute;   bottom: -3px;   left: 0;   width: 0;   height: 3px;   transition: all 0.3s ease-in-out; }  a:hover {  background-position: 0; }  a:hover::before {   width:100%; }

We can’t do text-decoration-color: rainbow, but we can fake it with a little background magic mixed with linear gradients.

First, we remove the link’s text-decoration:

a {   text-decoration: none; }

Now for those gradients. We chain two linear gradients together on the same background property. One gradient is the initial color before hover. The second is the rainbow on hover.

a {   background:     linear-gradient(       to right,       rgba(100, 200, 200, 1),       rgba(100, 200, 200, 1)     ),     linear-gradient(       to right,       rgba(255, 0, 0, 1),       rgba(255, 0, 180, 1),       rgba(0, 100, 200, 1)   ); }

Let’s make the background size a mere 3px tall so it looks like, you know, an underline. We can size both gradients together on the background-size property so that the initial gradient is full width and 3px tall, and the rainbow is zero width.

a {   background:     linear-gradient(       to right,       rgba(100, 200, 200, 1),       rgba(100, 200, 200, 1)     ),     linear-gradient(       to right,       rgba(255, 0, 0, 1),       rgba(255, 0, 180, 1),       rgba(0, 100, 200, 1)   );   background-size: 100% 3px, 0 3px; }

Now we can position the background gradients — at the same time on the background-position property — so that the first gradient is fully in view and the rainbow is pushed out of view. Oh, and let’s make sure the background isn’t repeating while we’re at it.

a {   background:     linear-gradient(       to right,       rgba(100, 200, 200, 1),       rgba(100, 200, 200, 1)     ),     linear-gradient(       to right,       rgba(255, 0, 0, 1),       rgba(255, 0, 180, 1),       rgba(0, 100, 200, 1)   );   background-size: 100% 3px, 0 3px;   background-position: 100% 100%, 0 100%;   background-repeat: no-repeat; }

Let’s update the background-size on hover so that the gradients swap values:

a:hover {   background-size: 0 3px, 100% 3px; }

And, finally, a little transition when the hover takes place:

a {   background:     linear-gradient(       to right,       rgba(100, 200, 200, 1),       rgba(100, 200, 200, 1)     ),     linear-gradient(       to right,       rgba(255, 0, 0, 1),       rgba(255, 0, 180, 1),       rgba(0, 100, 200, 1)   );   background-size: 100% 3px, 0 3px;   background-position: 100% 100%, 0 100%;   background-repeat: no-repeat;   transition: background-size 400ms; }

Voilà!

Geoff Graham actually covered this same one recently when he dissected Adam Argyle’s slick hover effect. In his demo, a background color enters from the left behind the link, then exits to the right on mouse out.

My version pares down the background so it’s more of an underline.

a {   position: relative; }  a::before {     content: '';     position: absolute;     width: 100%;     height: 4px;     border-radius: 4px;     background-color: #18272F;     bottom: 0;     left: 0;     transform-origin: right;     transform: scaleX(0);     transition: transform .3s ease-in-out;   }  a:hover::before {   transform-origin: left;   transform: scaleX(1); }

That’s not the only way to accomplish this! Here’s another one by Justin Wong using background instead:

Geoff also has a roundup of CSS link hover effects, ranging from neat to downright absurd. Worth checking out!

Have a blast linking!

There are a lot of options when it comes to creating your own hover effect for in-line links with CSS. You can even play with these effects and create something new. I hope you liked the article. Keep experimenting!


6 Creative Ideas for CSS Link Hover Effects originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , ,
[Top]

Adam Argyle’s Sick Mouse-Out CSS Hover Effect

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.”

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!


Adam Argyle’s Sick Mouse-Out CSS Hover Effect originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , , ,
[Top]

Want to Write a Hover Effect With Inline CSS? Use CSS Variables.

The other day I was working on a blog where each post has a custom color attached to it for a little dose of personality. The author gets to pick that color in the CMS when they’re writing the post. Just a super-light layer of art direction.

To make that color show up on the front end, I wrote the value right into an inline style attribute on the <article> element. My templates happened to be in Liquid, but this would look similar in other templating languages:

{% for post in posts %} <article style="background: {{post.custom_color}}">   <h1>{{post.title}}</h1>   {{content}} </article> {% endfor %}

No problem there. But then I thought, “Wouldn’t it be nice if the custom color only showed up when when hovering over the article card?” But you can’t write hover styles in a style attribute, right?

My first idea was to leave the style attribute in place and write CSS like this:

article {   background: lightgray !important; } article:hover {   /* Doesn't work! */   background: inherit; }

I can override the inline style by using !important, but there’s no way to undo that on hover.

Eventually, I decided I could use a style attribute to get the color value from the CMS, but instead of applying it right away, store it as a CSS variable:

<article style="--custom_color: {{post.custom_color}}">   <h1>{{post.title}}</h1>   {{content}} </article>

Then, that variable can be used to define the hover style in regular CSS:

article {   background: lightgray; } article:hover {   /* Works! */   background: var(--custom_color); }

Now that the color value is saved as a CSS variable, there are all kinds of other things we can do with it. For instance, we could make all links in the post appear in the custom color:

article a {   color: var(--custom_color); }

And because the variable is scoped to the <article> element, it won’t affect anything else on the page. We can even display multiple posts on the same page where each one renders in its own custom color.

Browser support for CSS variables is pretty deep, with the exception of Internet Explorer. Anyway, just a neat little trick that might come in handy if you find yourself working with light art direction in a CMS, as well as a reminder of just how awesome CSS variables can be.


The post Want to Write a Hover Effect With Inline CSS? Use CSS Variables. appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]