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

Published: (May 2, 2026 at 08:44 PM EDT)
3 min read
Source: Dev.to

Source: Dev.to

Animated floating hearts demo

1. The HTML Structure

The markup is straightforward: a wrapper that acts as the “sky” and a set of <div class="heart"> elements that become the hearts.
The wrapper must have position: relative; and overflow: hidden; so the hearts stay contained and disappear smoothly when they exit the top boundary.

<div class="sky">
    <div class="heart x1"></div>
    <div class="heart x2"></div>
    <div class="heart x3"></div>
    <!-- add more hearts as needed -->
</div>

<h2>Our customers love us!</h2>

2. Drawing a Heart with Pure CSS

A heart is created with a single element and its ::before and ::after pseudo‑elements.
The technique builds a square and adds two overlapping pill‑shaped circles.

.heart {
    position: relative;
}

/* Top halves of the heart */
.heart::before,
.heart::after {
    position: absolute;
    content: "";
    left: 18px;
    top: 0;
    width: 18px;
    height: 30px;
    background: #CC2022;               /* Heart colour */
    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%;
}

3. Creating the Movement (Keyframes)

Hearts need to float upward and sway side‑to‑side simultaneously. Two keyframe animations handle this:

/* Vertical movement */
@keyframes moveclouds {
    0%   { top: 500px; }
    100% { top: -500px; }
}

/* Horizontal sway */
@keyframes sideWays {
    0%   { margin-left: 0px; }
    100% { margin-left: 50px; }
}

4. Adding Variety

If every heart behaved identically, the effect would look robotic. By assigning different classes (.x1.x16) we vary size, opacity, horizontal start position, and animation speed.

/* Small, semi‑transparent, fast */
.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, slower */
.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;
}

What these properties do

  • left – Distributes hearts horizontally across the container.
  • transform: scale() – Adjusts size to create depth (parallax effect).
  • opacity – Controls perceived distance by fading distant hearts.
  • animation – Runs both the vertical and horizontal animations with class‑specific durations, giving an organic, randomized look.

Conclusion

By grouping the hearts inside a container with overflow: hidden, you get a lightweight, visually appealing floating‑hearts effect that doesn’t hurt performance. The code can be adapted to any framework (e.g., Alpine.js) and customized by changing background colours, animation speeds, or the number of heart instances.

Happy coding!

0 views
Back to Blog

Related posts

Read more »

Making my own framework. Any tips?

!Cover image for Making my own framework. Any tips?https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fde...