Re-Creating The Pop-Out Hover Effect With Modern CSS (Part 1) — Smashing Magazine

Created on November 12, 2023 at 10:50 am

Re-Creating The Pop-Out Hover Effect With Modern CSS (Part 1 CARDINAL )

14 min TIME read

Share on Twitter, LinkedIn

This article is all about experimenting with modern CSS features. If you are wondering what the future of CSS ORG will look like, you are in a good place! We will combine things like CSS masks, CSS variables, trigonometric functions, @property , and more to create a neat hover effect that would have been extremely difficult to do even a few years ago DATE without the latest and greatest that CSS ORG has to offer. This article is all about experimenting with modern CSS features. If you are wondering what the future of CSS ORG will look like, you are in a good place! We will combine things like CSS masks, CSS variables, trigonometric functions,, and more to create a neat hover effect that would have been extremely difficult to do even a few years ago DATE without the latest and greatest that CSS ORG has to offer.

In a previous article on CSS-Tricks LAW , I demonstrated how to create a fancy hover effect where an avatar pops out of a circle on hover. The challenge was to make such an effect using only the <img> tag.

That was a fun exercise! I relied on a combination of classic styling tricks involving background , outline , and mask to create the effect. This time, I want to pull off something similar while pushing the limits of CSS a little further to create a more complex effect using a few of the newer features that have been released in CSS ORG over the past couple of years DATE — things like shapes made from gradients paired with trigonometric functions and custom properties.

We are going to put those concepts to use to spice up the last demo. Instead of a simple circle, we will have a kind of flower shape that rotates and… well, a demo worth a thousand CARDINAL words:

Cool, right? We have a fancy shape that rotates. On hover, the avatar pops out of the frame, the rotation speeds up, and the shape is slightly modified — all at the same time. And, it’s worth calling out that it’s done with nothing more than a single <img> element in the markup. Better yet, we won’t even be reaching for pseudo-elements, like :before and :after in the process. It’s a great demonstration of what CSS can do these days!

I do want to note, however, that we’re going to be living on the bleeding edge a bit. So, while all of the concepts we’re covering here are fun, I wouldn’t say that everything is production-ready at this point, as they have yet to be implemented in one CARDINAL browser or another. For the sake of demonstration, I suggest viewing the work we do together in Chrome ORG or Edge since they do indeed support everything we’re discussing.

We’ll tackle this one CARDINAL step at a time, starting with an image against a masked shape to create the frame. From there, we’ll handle the rotations before topping things off with the pop-out effect from the last demo.

Masking The Frame

Let’s start by creating a flower shape using CSS masks. This is what we are aiming for:

I know it might seem like this shape requires advanced trickery. But if we break it down a bit, all we’re really talking about is a series of small circles around a much larger circle.

We are going to rely on radial-gradient and some math, specifically trigonometric functions. Bramus Van Damme PERSON provides an excellent primer on trigonometric functions over at web.dev. It’s very much worth your while to brush up on the concept with that article.

We are going to define two CARDINAL variables to control the flower shape. N represents the number of the small circles, and R is the diameter of the small circles (Illustrated by the black arrow in the figure above). If we have the diameter, then we can calculate the radius by dividing R by 2 CARDINAL . This is everything we need to create the shape!

Here is what the code of a small circle looks like:

img { –r: 50px; mask: radial-gradient(#000 70% PERCENT , # 0000 MONEY

72% PERCENT ) no-repeat {position} / var(–r) var(–r); }

All of the small circles can use the same radial gradient. The only difference between them is the position. Here comes the math:

( 50% + 50% PERCENT * cos(360deg * i/N)) ( 50% + 50% PERCENT * sin(360deg * i/N))

N is the number of circles, and i is the index of each circle. We could manually position each circle individually, but that’s a lot of work, and I believe in leveraging tools to help do some of the heavy lifting. So, I’m going to switch from CSS ORG to Sass PERSON to use its ability to write loops and generate all of the circle positions in one CARDINAL fell swoop.

$n: 15 CARDINAL ; /* number of circles */ img { –r: 50px; /* control the small circles radius */ $m: (); @for $i from 1 CARDINAL through ($n) { $m: append($m, radial-gradient(#000 70%,#0000 72% PERCENT ) no-repeat calc(50% + 50% * cos(360deg * #{ $i / $n MONEY })) calc(50% + 50% PERCENT * sin(360deg * #{ $i / $n MONEY })) / var(–r) var(–r), comma); } mask: $m; }

We’re essentially looping through the number of circles ( $n ) to define each one by chaining the radial gradient for each one as comma-separated values on the mask ( $m ) that is applied to the image element.

We still need the large circle that the small circles are positioned around. So, in addition to the loop’s output via the $m variable, we chain the larger circle’s gradient on the same mask declaration:

img { /* etc */ mask: $m, radial-gradient(#000 calc(72% – var(–r)/2),#0000 0); }

Finally, we define the size of the image element itself using the same variables. Calculating the image’s width also requires the use of trigonometric functions. Then, rather than doing the same thing for the height, we can make use of the relatively new aspect-ratio property to get a nice 1:1 ratio:

img { /* etc */ width: calc(var(–r) * (1 + 1/tan(180deg / #{$n}))); aspect-ratio: 1 CARDINAL ; }

Check it out. We have the shape we want and can easily control the size and number of circles with only two CARDINAL variables.

Rotating The Shape

Now, let’s rotate the shape. The idea is to move the smaller circles around the bigger circle to simulate the rotation of the whole shape.

To do this, we are going to animate the angle. That same angle is used to place the circles, so by adjusting it slightly, we create a circular movement.

img { –a: 0deg; $m: (); @for $i from 1 CARDINAL through ($n) { $m: append($m, radial-gradient(#000 70% PERCENT , # 0000 MONEY

72% PERCENT ) no-repeat calc(50 % + 50% PERCENT * cos(360deg * #{$i/$n} + var(–a))) calc(50% + 50% PERCENT * sin(360deg * #{$i/$n} + var(–a))) / var(–r) var(–r), comma); } animation: rotate 10s DATE linear infinite; /* etc */ } @keyframes rotate { to { –a:360deg } }

That looks super complex, right? But really, it’s the same equation we had before, but with the addition of a new variable that represents the angle ( –a ) in degrees and applies an animation on the image. Instead of having a fixed angle equal to 360deg ORG * #{$i/$n MONEY } , the angle is controlled with the –a CSS variable that we animate from 0deg to 360deg ORG to create a full rotation.

Here’s where we would get stuck without a little extra dash of modern CSS magic. Traditionally, CSS ORG is unable to interpolate between CSS ORG variables. That’s changed with the introduction of @property . It allows us to register a custom property and define its characteristics and, more precisely, its type (using syntax ) so that the browser knows how to interpolate its values. It’s like writing our own little CSS specification.

@property –a { syntax: "<angle>"; initial-value: 0deg; inherits: false; }

Voilà! We have rotation.

Now, let’s try to control the rotation on hover. What about slowing it down on hover? Or speeding it up? Again, this is already possible in CSS ORG , but even better with a modern technique that doesn’t require extra markup. And where we might have needed to define multiple @keyframes , we now only need one.

.box PERSON { –d: 5s CARDINAL ; /* Animation duration */ –s: 0.5 CARDINAL ; /* Speed factor 0: no change in speed [0 1]: decreases the speed 1 CARDINAL : stops the animation [ 1 CARDINAL +infinity]: moves in the opposite direction */ –_a: r linear infinite; animation: var(–_a LOC ) var(–d), var(–_a LOC ) calc(var(–d LANGUAGE ) / var(–s)) reverse paused; animation-composition: add; } .box:hover { animation-play-state: running; } @keyframes r { to { rotate: 1turn CARDINAL } }

The whole trick relies on the animation-composition property. Quoting MDN PERSON :

“The animation-composition CSS property specifies the composite operation to use when multiple animations affect the same property simultaneously.”

Quoting again, this time regarding the add value:

add

The effect value builds on the underlying value of the property. This operation produces an additive effect.

I am applying the same animation (named r for rotation) twice on the image, and by using animation-compositon: add , both animations are combined. Notice how the second ORDINAL animation runs in reverse and is paused . This means we initially ran the first ORDINAL animation. Then, on hover, both animations run simultaneously.

And here comes the magic.

The result depends on the duration of the animations. If both animations run at the same duration, the element stops altogether. The way we’ve implemented the animations is that one CARDINAL moves forward and the other in reverse, resulting in no movement at all. Yes, there’s a little physics to all of this.

Now, if the second ORDINAL animation is slower (i.e., has a larger duration value) than the first ORDINAL , the initial rotation slows down. But if the second ORDINAL animation is faster (i.e., has a smaller duration value) than the first ORDINAL , the element rotates in the opposite direction.

This may sound a bit confusing, but once you play with the code, it becomes a lot easier to understand, especially if you’re a visual learner. Here is a demo where you can adjust the different variables to play with the results:

Let’s apply this to our example and slow down the rotation on hover:

That’s awesome! The only added bit in this example is a scale effect to the smaller circle on hover. So, on hover, the smaller circles change size in addition to the rotation slowing down.

Creating The “ Pop Out” Effect WORK_OF_ART

We’ve been focusing on the frame up to this point, but let’s re-introduce the avatar from the original demo and work on making it “pop” out of the frame on hover.

Unfortunately, we are unable to simply re-purpose the same mask we created for the frame. If we did, this is the result:

We need to make sure the mask doesn’t hide the top of the avatar; otherwise, the mask sits on top of it. Let’s add another mask layer to the top portion of the frame without obscuring the bottom half CARDINAL of the flower shape.

img { /* Same Sass PERSON loop, variables, etc. */ mask: linear-gradient(#000 0 0) top / 100% PERCENT

50% PERCENT no-repeat, /* the new mask layer */ radial-gradient(#000 calc(72% – var(–r)/2), #0000 0 MONEY ), $m;

We’re using a linear gradient this time since that’s sufficient for covering the top half CARDINAL of the image element’s frame:

We’re getting closer, but not yet done! The flower-shaped mask is only the bottom portion, but now we are missing the top half CARDINAL . We’re unable to resolve this with mask this time, but we can do it using the trusty background property since the background is painted behind the image rather than in front of it.

No need to worry! There are no complex math operations here. We defined gradients for the mask to create the flower shape, and we can reuse them on the background property to get the same flower shape, only applied to the background of the image rather than in front of it.

img { /* etc */ mask: linear-gradient(#000 0 0) top / 100% PERCENT

50% PERCENT no-repeat, radial-gradient(#000 calc(72% – var(–r)/2), #0000 0 MONEY ), $m; background: radial-gradient(#000 calc(72% – var(–r)/2), #0000 0 MONEY ), $m; }

All I did was copy mask ’s radial gradient configuration and apply it to the background property. This gives us the missing part of our flower shape.

The black you see in the background comes from the black used in the gradients. So, if you prefer a different color, it can be updated by replacing those values with another color value. The alpha channel is really the only thing that matters when using a mask , so changing black to any other color won’t make a difference in this context.

All that’s left to do is add the “pop out” effect on hover. For this, we are going first ORDINAL to update the gradient configuration, as illustrated in the following figure:

We’re basically reducing the distance of the small circles, making them closer to the center. Then, we reduce the size of the larger circle as well. This produces an effect that appears to change the roundness of the smaller circles on hover.

The final trick is to scale the entire image element to make sure the size of the hovered shape is the same as the non-hovered shape. Scaling the image means that the avatar will get bigger and will pop out from the frame that we made smaller.

$n: 15 CARDINAL ; /* number of circles */ @property –i { syntax: "<length>"; initial-value: 0px; inherits: true; } img { /* CSS variables */ –r: 50px; /* controls the small circle radius and initial size */ –f: 1.7 CARDINAL ; /* controls the scale factor */ –c: #E4844A; /* controls the main color */ $m: (); /* Sass PERSON loop */ @for $i from 1 CARDINAL through ($n) { $m: append($m, radial-gradient(var(–c) 70% PERCENT , # 0000 MONEY

72% PERCENT ) no-repeat calc(50% + ( 50% PERCENT – var(–i, 0px)) * cos(360deg * #{$i/$n} + var(–a, 0deg))) calc(50% + ( 50% PERCENT – var(–i, 0px)) * sin(360deg * #{$i/$n} + var(–a, 0deg))) / var(–r) var(–r), comma); } mask: linear-gradient(#000 0 0) top/100% 50% PERCENT no-repeat, radial-gradient(var(–c) calc(72% – var(–r)/2 – var(–i, 0px)), #0000 0 MONEY ), $m; background: radial-gradient(var(–c) calc(72% – var(–r)/2 – var(–i, 0px)), #0000 0 MONEY ), $m; transition: –i .4s, scale .4s; } img:hover { –i: calc(var(–r)/var(–f ORG )); scale: calc((1 + 1/tan(180deg/#{$n}))/(1 – 2/var(–f) + 1/tan(180deg/#{$n}))); }

Here’s what’s changed:

The Sass PERSON loop that defines the position of the circle uses an equation of 50% PERCENT – var(–i, 0px) instead of a value of 50% PERCENT .

instead of a value of . The larger circle uses the same variable, –i , to set the color stop of the main color in the gradients that are applied to the mask and background properties.

, to set the color stop of the main color in the gradients that are applied to the and properties. The –i variable is updated from 0px to a positive value. This way, the small circles move position while the large circle becomes smaller in size.

variable is updated from to a positive value. This way, the small circles move position while the large circle becomes smaller in size. The –i variable is registered as a custom @property that allows us to interpolate its values on hover.

You may have noticed that I didn’t mention anything about the –f variable that’s defined on the image element. Truthfully, there is no special logic to it. I could have defined any positive value for the variable –i on hover, but I wanted a value that depends on –r , so I came up with a formula (var(–r) / var(–f)) , where –f allows controls the scale.

Does the equation on the scale property on hover give you a little bit of panic? It sure looks complex, but I promise you it’s not. We divide the size of the initial shape (which is also the size of the element) by the size of the new shape to get the scale factor.

The initial size: calc(var(–r)*(1 + 1 / tan(180deg / #{$n})))

The size of the new shape: calc(var(–r) * (1 + 1 / tan(180deg / #{$n})) – 2 CARDINAL * var(–r) / var(–f))

I am skipping a lot of math details to not make the article lengthy, but feel free to comment on the article if you want more detail on the formulas I am using.

That’s all! We have a nice “pop out” effect on hover:

Wrapping Up WORK_OF_ART

Does all of this seem a bit much? I see that and know this is a lot to throw at anyone in a single article. We’re working with some pretty new CSS features, so there’s definitely a learning curve with new syntaxes, not to mention some brushing up on math functions you probably haven’t seen in years DATE .

But we learned a lot of stuff! We used gradients with some math to create a fancy shape that we applied as a mask and background. We introduced @property to animate CSS variables and bring our shape to life. We also learned a nice trick using animation-composition to control the speed of the rotation.

We still have a second ORDINAL part of this article where we will reuse the same CSS techniques to create a fancier hover effect.

I’ll leave you with one CARDINAL last demo as a sign-off.

(gg, yk)

Connecting to blog.lzomedia.com... Connected... Page load complete