DEV Community

La Rainne Pasion
La Rainne Pasion

Posted on

Make a beating heart using CSS: beginner-friendly tutorial

Did Valentine’s Day slip your mind this year? You’re probably not the only one. You could go the ol’ procrastinator’s route and buy your significant other some last-minute chocolate or flowers. Or, you could build them something quick and simple but still makes them think, “Dang, I’m in love with a thoughtful genius.” Enter: a beating heart animation made with CSS.

GIF of a red beating heart animation

CSS art can be complex and daunting, but the foundation of this easy tutorial is just a couple of shapes: a square and two circles.

A heart shape made out of a square and two circles

We start by creating a div in our HTML. I gave mine a class of heart just to really solidify that’s what we’re making (and what we risk breaking if we don’t give our loved ones anything for Valentine’s).

<div class="heart"></div>
Enter fullscreen mode Exit fullscreen mode

Let’s make sure this heart is centered on the page. In your CSS, set the body (the container of the heart) to display: flex and center the content inside it using the justify-content and align-items properties.

body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
}
Enter fullscreen mode Exit fullscreen mode

Next, let’s give the div some color and dimensions. I chose a classic red and set the height and width to 100 pixels each. Regardless of how big you make your heart, the height and width should be equal so you end up with a square. Rotate this square -45 degrees to get the pointed end on the bottom of the heart.

.heart {
    height: 100px;
    width: 100px;
    background-color: red;
    transform: rotate(-45deg);
    position: relative;
}
Enter fullscreen mode Exit fullscreen mode

To create the circles that form the top of the heart, we use the ::before and ::after pseudo-elements to generate two new shapes. Set them to the same height, width, and color as the original element and give them a border-radius of 50% to turn them into circles. Set their position to absolute.

.heart::before, 
.heart::after {
    content: "";
    height: 100px;
    width: 100px;
    background-color: red;
    border-radius: 50%;
    position: absolute;
}
Enter fullscreen mode Exit fullscreen mode

Now that we have these circles, we need to move them into place to create a heart. Bring the .heart::before pseudo-element halfway up from its starting position by offsetting it -50 pixels (or half of however tall your .heart::before is) from the top. Notice that it moves diagonally toward the top left of the screen because of how the original .heart element was rotated -45 degrees. To complete the heart, bring the .heart::after pseudo-element halfway to the right of its starting position by offsetting it 50px from the left.

.heart::before {
    top: -50px;
    left: 0px;
}

.heart::after {
    left: 50px;
    top: 0px;
}
Enter fullscreen mode Exit fullscreen mode

Time for the fun part: let's make this baby beat! We'll use @keyframes and name the animation heartbeat. Let's transform the heart to grow 1.25x its original size when the animation is 25% through its duration, and then grow even bigger when the animation is 45% of the way through. This change in size should give us that beating effect. Make sure you keep the -45 degrees rotation in your transform property so your heart doesn't tilt during the animation.

@keyframes heartbeat {
    0% {
        transform: scale(1)
            rotate(-45deg);
    }

    25% {
        transform: scale(1.25)
            rotate(-45deg);
    }

    45% {
        transform: scale(1.5)
            rotate(-45deg);
    }
} 
Enter fullscreen mode Exit fullscreen mode

The last thing we need to do now is define the duration and iteration count for our animation. Let's go back to our .heart element and set the animation property to specify our heartbeat animation. Then, let's set the duration to 1 second and give it an infinite loop.

.heart {
    height: 100px;
    width: 100px;
    background-color: red;
    transform: rotate(-45deg);
    position: relative;
    animation: heartbeat 1s infinite;
}
Enter fullscreen mode Exit fullscreen mode

And there you have it: a CSS heart animation that will beat as infinitely as your love will (hopefully) last. Happy Valentine's Day!

Top comments (7)

Collapse
 
rolandixor profile image
Roland Taylor

Love it!

Tempted to try it and add sparkles (because why not) lol

Collapse
 
larainnepasion profile image
La Rainne Pasion

Oh my gosh, please add sparkles and let me know how it goes!

Collapse
 
rolandixor profile image
Roland Taylor • Edited

FINALLY got around to this lol

<body>
    <div class="heart">
      <div class="sparkle">
      </div>
      <div class="sparkle">
      </div>
      <div class="sparkle">
      </div>
      <div class="sparkle">
      </div>
      <div class="sparkle">
      </div>
      <div class="sparkle">
      </div>
    </div>
  </body>
Enter fullscreen mode Exit fullscreen mode

body {
  align-items: center;
  display: flex;
  justify-content: center;
  height: 100vh;
}

@keyframes heartbeat {
  0% {
    opacity: 1;
    transform: rotate(-45deg) scale(1);
  }
  50% {
    opacity: .735;
    transform: rotate(-45deg) scale(.765);
  }
  100% {
    opacity: 1;
    transform: rotate(-45deg) scale(1);
  }
}

.heart {
  animation: heartbeat 1.8235s infinite;
  aspect-ratio: 1/1;
  height: 25vh;
  background-color: red;
  position: relative;
  transform: rotate(-45deg) scale(5);
}

.heart::after,
.heart::before {
  aspect-ratio: 1/1;
  background-color: red;
  border-radius: 50%;
  content: '';
  height: 25vh;
  position: absolute;
}

.heart::after {
  left: 12.5vh;
}

.heart::before {
  top: -12.5vh;
}

@keyframes sparkle {
  0% {
    border: 3px solid red;
    transform: scale(0);
  }
  50% {
    border: 1px solid white;
  }
  100% {
    border: 1px solid transparent;
    transform: scale(1) translateX(7.5vh) translateY(-7.5vh);
  }
}

.sparkle {
  animation: sparkle 2.75s infinite;
  aspect-ratio: 1/1;
  border-radius: 50%;
  position: absolute;
  width: 3.35vh;
  z-index: 1;
}

@keyframes sparklebars {
  0% {
    opacity: 1;
    transform: scale(1);
  }
  50% {
    opacity: 1;
    transform: scale(0);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes sparklebars2 {
  0% {
    opacity: 1;
    transform: scale(1) rotate(90deg);
  }
  50% {
    opacity: 0;
    transform: scale(0) rotate(90deg);
  }
  100% {
    opacity: 1;
    transform: scale(1) rotate(90deg);
  }
}

.sparkle::after,
.sparkle::before {
  animation: sparklebars .5s infinite;
  background-color: white;
  content: '';
  height: 5px;
  inset: 50% -50% 0 -50%;
  position: absolute;
}

.sparkle:after {
  animation: sparklebars2 .5s infinite;
  transform: rotate(90deg);
}

.sparkle:nth-child(1) {
  inset: calc(50% - 2.5vh);
}

.sparkle:nth-child(2) {
  inset: calc(50% - 2.5vh) 0 calc(50% - 2.5vh) auto;
}

.sparkle:nth-child(3) {
  inset: 0 calc(50% - 2.5vh) calc(50% - 2.5vh) auto;
}

.sparkle:nth-child(4) {
  inset: 100% 25% auto calc(50% - 2.5vh);
}

.sparkle:nth-child(5) {
  inset: 75% 100% auto auto;
}

.sparkle:nth-child(6) {
  inset: 75% 50% auto auto;
}

Enter fullscreen mode Exit fullscreen mode

The result:

Image description

I didn't bother to add randomness to their positions since that would require JS (unless there's some new CSS features I missed lol). Some day when I'm not busy (or lazy), I might circle back to this and spruce it up some more, just for fun.

Collapse
 
mpfdev profile image
Matheus 🇧🇷

Thank you for this, I loved your writing style

Collapse
 
larainnepasion profile image
La Rainne Pasion

Thanks for reading, Matheus! I appreciate the support.

Collapse
 
sique976 profile image
San D.

Just Great! Congrats!

Collapse
 
sique976 profile image
San D.

My comments gone!? Congrats! Just did it. Going to put on my git repo. Surely going to mention your name...