A Modern-CSS Refresher: Building a Flower-Frame Avatar Hover Effect

This article is an exercise in pushing modern CSS features to their limits. We will combine techniques like CSS masks, trigonometric functions, CSS variables, @property, and animation-composition in a single, cohesive effect that is significantly more complex than what was possible just a few years ago. The goal: a flower-shaped frame that rotates and lets an avatar "pop out" on hover, built only with a single <img> element in the HTML.

This builds on a previous trick that used background, outline, and mask alongside an avatar. Now, we're going to replace the simple circle with a dynamic, multi-layered shape. The final result recognizes no pseudo-elements; it is a testament to the power of modern CSS.

Note: This is a deep dive on the front end of CSS support. Many of the features we leverage (like @property and trigonometric functions) are brand new and may not be available in all browsers. For the best experience, view the demos in a Chromium-based browser like Chrome or Edge, as they support the full suite of technologies we're testing.

We will proceed step-by-step. First, we'll build the complex frame using the mask property. Then, we'll create a dynamic rotation using a keyframe and CSS variables. Finally, we’ll add the "pop-out" hover effect that brings the whole design together.

The Geometric Foundation of the Frame

Our visual goal is a flower shape. This isn't a single complex polygon, but rather the combination of several small circles arranged around a larger central circle.

We will implement the frame using radial-gradient() and CSS trigonometric functions. Rather than place each circle manually, we'll leverage tools to streamline the repetitive geometry. We'll define two critical Sass variables:

  • $n: the number of small circles making up the flower.
  • $r: the diameter of each of those small circles.

From just these two variables, we can calculate the exact position of each circle. Instead of writing each radial-gradient() by hand, we'll use a Sass @for loop to chain them together and define our $m variable.

@for $i from 1 through $n {
  $m: append(
    $m,
    radial-gradient(50% 50%, #000 61%, #dada120 62%) 
    #{50% + $r * cos(360deg * $i / $n)} 
    #{50% + $r * sin(360deg * $i / $n)}
  ,  comma);
}

After the loop finishes, we chain on the gradient for the larger central circle to the $m mask variable. The mask is then applied to the image element. Calculating dimensions requires a bit more trigonometry, so for the image’s width, we find the total size using the radius R. For the height, we use the new aspect-ratio property to maintain a perfect square.

border-radius: 50%;
max-width: 100%;
aspect-ratio: 1;
width: calc(var(--r) * (1 + 1 / sin(180deg / #{$n})));
height: auto;
-mask: $m;

The output is a scalable flower frame driven entirely by just $n (number of circles) and $r (diameter).

Spinning the Design with @property

Now we rotate it. The core idea here is to animate the angle used for positioning. If the small circle's positions are controlled by a variable, we can rotate those positions smoothly to make the whole flower spin. That variable is the CSS custom property --a.

We update the position in the Sass loop to incorporate this new angle variable.

#{50% + var(--b1) * cos(var(--a))}
#{50% + var(--b1) * sin(var(--a))}

This is a significant jump – a static Sass value is now a CSS variable that we can animate! And for it to actually transition numerically, we must rely on the @property at-rule. By registering a property, you can provide it a type (syntax) that lets the browser know how to interpolate between values.

@property --a {
  syntax: '';
  inherits: false;
  initial-value: 0deg;
}

With this, we can use an infinite CSS keyframe animation (the same one we used before) on the --a property. This moves it from 0deg to 360deg, creating a seamless rotation. We achieved the loop with pure CSS, with just a couple of new wrapping variables.

Speeding Up (and Slowing Down) the Spin

Adding interactivity means we want to change this rotation on hover. But how do we control the speed with only a single element and a single animation? The solution is the powerful animation-composition property, which handles animations of the same property simultaneously.

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

The property has the option of add, where the effect value builds on the underlying value. We'll use two animations on the same element. The first one is infinite and runs forward. The second one is also infinite but runs in reverse and is paused by default.

The magic happens when they combine. With animation-composition: add, both are active. If the durations are identical, the forward and reverse animations cancel each other out and the flower freezes. But if the second animation is slower, the flower slows down but keeps spinning; if it is faster, the element reverses and spins in the opposite direction.

Switching the second animation to run on hover (by toggling its animation-play-state), we can control the animation post-@keyframes without engineering another keyframe. It's an elegant technique for complex time travel with minimal markup.

Handling the "Pop Out" With a Splitting Mask

Now the final step—ensuring the avatar visibly emerges from the frame. Early attempts reveal a core problem: if we try to mask the entire image, the avatar gets obscured. To hide only the bottom part of the frame while letting the top show, we must create a multi-part mask that lets the flower look authentic.

The solution is a clever four-part code architecture. On one side, the mask handles the bottom half, but it should use a linear-gradient() to hide that entire region from rendering trouble while the internal background-color provides the shape's visual identity.

Since the background shows through the top, we don't strictly use the mask for the top region. Instead, we manage multiple layered gradients simultaneously: a black mask for the vector containment, and a solid background-color for coloring. When we reduce the silhouette radius on hover – setting the small circles tighter – the frame completes its transition but the background paints in the missing gaps, creating that 3D pocket.

The end process smoothly shifts the geometry. When hovered, the top "flower petals" pull closer, the central disc shrinks dynamically, and a pixel-perfect scale factor moves the avatar over the new, smaller frame. It creates the slick 3D "pop-out" of the avatar from the ever-spinning flower.

Final Thoughts on the Technique

There’s no denying that this is a dense set of techniques. We’re working at the cutting edge of CSS, so the new syntaxes and the return of math functions you might not have touched in years add a real learning curve. But look at what we’ve accomplished: we’ve used gradients and arithmetic to craft a complex shape that serves as both mask and background. We’ve wired up @property to animate CSS variables and bring that shape to life, and we’ve leaned on animation-composition to fine-tune the rotation speed. Each piece is a building block you can reuse.

See the Pen [Pop out hover effect featuring Lea and Una](https://codepen.io/t_afif/pen/OJrLgeV) by Temani Afif.

See the Pen Pop out hover effect featuring Lea and Una by Temani Afif.

This approach isn’t just a one-off gimmick. The same CSS primitives—masking, custom-property animation, and composition control—are the foundation for more elaborate effects. In the second part of this series, we’ll extend these exact techniques to build a richer hover interaction with multiple moving parts.

Before we get there, here’s one final demo to show how the pieces snap together in a slightly different configuration:

Smashing Editorial