DEV Community

Ruben
Ruben

Posted on

How to Create an Animated Floating Hearts Effect (Pure CSS & HTML)

I recently built a dynamic testimonials component for my project at Coloring Maker and wanted to give it a little extra "magic." This is how I did it.

1. The HTML Structure

The structure is quite simple. We need a main wrapper that acts as our "sky" and a series of div elements that will become our hearts.

It is crucial that the main container has the position: relative; and overflow: hidden; properties. This ensures the hearts stay contained within the section and disappear smoothly as they cross the top boundary.

<div class="relative overflow-hidden min-h-80" style="position: relative; overflow: hidden; min-height: 20rem; background-color: #f8f9fa;">

    <div class="heart-animation">
        <div class="heart x1"></div>
        <div class="heart x2"></div>
        <div class="heart x3"></div>
        <div class="heart x16"></div>
    </div>

    <div style="position: relative; z-index: 10;">
        <h1>Our customers love us!</h1>
    </div>

</div>
Enter fullscreen mode Exit fullscreen mode

2. Drawing a Heart with Pure CSS

This is the fun part. We are not going to use SVGs or images for the main hearts. Instead, we'll draw them using a single div alongside the ::before and ::after pseudo-elements.

The technique consists of creating a square and overlapping two pill-shaped circles:

.heart {
    position: relative;
}

/* Creating the two top halves of the heart */
.heart:before,
.heart:after {
    position: absolute;
    content: "";
    left: 18px;
    top: 0;
    width: 18px;
    height: 30px;
    background: #CC2022; /* Your heart's color */
    border-radius: 30px 30px 0 0; /* Pill shape */
    transform: rotate(-45deg);
    transform-origin: 0 100%;
}

.heart:after {
    left: 0;
    transform: rotate(45deg);
    transform-origin: 100% 100%;
}
Enter fullscreen mode Exit fullscreen mode

3. Creating the Movement (Keyframes)

To make the effect look natural, our hearts need to do two things simultaneously:

Float upwards from the bottom.

Sway slightly from side to side (as if carried by a gentle breeze).
Enter fullscreen mode Exit fullscreen mode

To achieve this, we combine two @keyframes animations:

/* Vertical animation: Moves from 500px up to -500px */
@keyframes moveclouds {     
    0% { top: 500px; }
    100% { top: -500px; }
}

/* Horizontal animation: Gentle sway */
@keyframes sideWays {
    0% { margin-left: 0px; }
    100% { margin-left: 50px; }
}
Enter fullscreen mode Exit fullscreen mode

4. Adding Variety

If all the hearts rise at the exact same speed and are the same size, the animation will look robotic. To fix this, we create multiple classes (from .x1 to .x16) and apply different scale, opacity, and animation speed values to each one.

Here is an example of how to configure a few of them:

/* Small, semi-transparent, and fast heart */
.x1 {
    left: 5%;
    transform: scale(0.9);
    opacity: 0.6;
    animation: moveclouds 15s linear infinite, sideWays 4s ease-in-out infinite alternate;
}

/* Larger, more opaque, and slower heart */
.x2 {
    left: 25%;
    transform: scale(0.6);
    opacity: 0.9;
    animation: moveclouds 25s linear infinite, sideWays 5s ease-in-out infinite alternate;
}

/* Very fast heart on the far right */
.x5 {
    left: 88%;
    transform: scale(0.8);
    opacity: 0.3; 
    animation: moveclouds 7s linear infinite, sideWays 1s ease-in-out infinite alternate;
}
Enter fullscreen mode Exit fullscreen mode

What exactly is happening here?

  • left: Spreads the hearts horizontally across the container.

  • transform: scale(): Changes the size to create a sense of depth (parallax effect).

  • opacity: Makes some hearts look further away by fading them out.

  • animation: We run both animations (moveclouds and sideWays) but assign different durations to each class, creating an organic, randomized look.

5. Interactive Demo

Check out the interactive demo below to see all the pieces working together:

Conclusion

And that's it! By grouping these elements inside a container with overflow-hidden, you get a beautiful visual effect that won't punish your website's performance.

In my use case, I adapted this code to work with an Alpine.js component. If you want to see the final result in action, you can check out the testimonials area over at Coloring Maker. You can also easily tweak the background color of the pseudo-elements to match your brand, or play around with the keyframe speeds to fit the unique mood of your site.

Hope you like it! Happy coding!

Top comments (0)