Let’s create an “I Love You” Heart Card Animation using HTML, CSS, and JavaScript. 💖
This romantic and interactive project will feature a beautiful heart-shaped card that opens with a smooth animation and displays an “I Love You” message – perfect for Valentine’s Day or just to express love creatively through code.
We’ll use:
- HTML to structure the heart card and message.
- CSS to style the heart, add glowing effects, and create smooth opening animations.
- JavaScript to handle interactions, such as clicking to open the card or triggering heart animations.
This project is ideal for beginners and intermediate developers who want to learn animation and interactivity in a fun, heartfelt way. Let’s get started and bring some love to life through code! 💌❤️
HTML :
This HTML code creates a cute “I Love You” interactive heart card. It loads external CSS and JS libraries, then builds a card with two sides that flip using CSS. Each side shows a heart shape made from simple divs and a small console.log('I Love You') code snippet for a coding aesthetic. The card has a “Click me” text and plays an audio file at the bottom. The page uses separate CSS (style.css) for design and animations, and JavaScript (script.js) for interactions, while Highlight.js styles the code snippet.
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>I love you heart card | @coding.stella</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/styles/atom-one-dark.min.css'><link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="content">
<div id="background"></div>
<div class="card-wrapper">
<div class="card card-main">
<div class="side side-a">
<div class="back">
<div class="heart">
<div class="heart-half">
<div class="circle"></div>
<div class="rect"></div>
</div>
<pre class="ijustcode"><code class="language-javascript"> console.log(
'I Love You'
);</code></pre>
</div>
</div>
<div class="front">
<div class="heart">
<div class="heart-half">
<div class="circle"></div>
<div class="rect"></div>
</div>
<pre class="ijustcode"><code class="language-javascript"> console.log(
'I Love You'
);</code></pre>
</div>
</div>
</div>
<div class="side side-b">
<div class="back">
<div class="heart">
<div class="heart-half">
<div class="circle"></div>
<div class="rect"></div>
</div>
<pre class="ijustcode"><code class="language-javascript"> console.log(
'I Love You'
);</code></pre>
<div class="title">Click me</div>
</div>
</div>
<div class="front">
<div class="heart">
<div class="heart-half">
<div class="circle"></div>
<div class="rect"></div>
</div>
<pre class="ijustcode"><code class="language-javascript"> console.log(
'I Love You'
);</code></pre>
<div class="title">Click me</div>
</div>
</div>
</div>
</div>
</div>
<audio controls="controls" src="https://toptrack-assets.s3.eu-central-1.amazonaws.com/i-just-called-comp.mp3">Your browser does not support the <audio> element</audio>
</div>
<script src='https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/highlight.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/granim/2.0.0/granim.min.js'></script><script src="./script.js"></script>
</body>
</html>
CSS :
This CSS controls the layout, heart design, and flip animation of the card. It centers the card on the screen and sets the card to rotate in 3D when the wrapper gets the .active class. Each side of the card has a front and back that flip using rotateY. The heart shape is made using one circle and one rectangle rotated to form a heart, and their colors change when the card opens. Code snippets are styled with Highlight.js, and the “Click me” text is positioned on the heart. Confetti animations use keyframes to drop pieces from top to bottom with different speeds. The gradient, audio, and confetti stay behind the card using z-index. Overall, this CSS handles the flipping effect, heart shape, colors, and confetti animation.
.content {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
.card-wrapper {
width: 560px;
}
.card-wrapper .card {
position: relative;
width: 280px;
height: 497.7777777778px;
transform: perspective(1000px) translate(50%) scale(0.5) rotateY(0deg);
transform-origin: 50px center;
transition: 1s cubic-bezier(0, 0, 0, 0.94);
}
.card-wrapper .card .side {
background: transparent;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
transform: perspective(1000px) rotateY(0);
transform-origin: right center;
transform-style: preserve-3d;
transition: 1s cubic-bezier(0, 0, 0, 0.94);
}
.card-wrapper .card .side.side-a {
border-radius: 10px 0 0 10px;
}
.card-wrapper .card .side.side-b {
border-radius: 10px 0 0 10px;
}
.card-wrapper .card .side .back,
.card-wrapper .card .side .front {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
backface-visibility: hidden;
transition: 0 0.5s cubic-bezier(0, 0, 0, 0.94);
overflow: hidden;
}
.card-wrapper .card .side .front {
z-index: 2;
transform-origin: center;
}
.card-wrapper .card .side .back {
transform: rotateY(180deg);
}
.card-wrapper .card .side .back .heart {
transform: rotateY(180deg);
max-height: 100%;
}
.card-wrapper .card .side.side-b .front .heart .circle,
.card-wrapper .card .side.side-b .front .heart .rect {
background: #f9bec7;
}
.card-wrapper .card .side.side-b .back .heart .title {
display: none;
}
.card-wrapper.active .card {
transform: perspective(1000px) translate(0%) scale(1) rotateY(0);
}
.card-wrapper.active .card .side.side-b {
transform: perspective(1000px) rotateY(180deg);
}
.card-wrapper.active .card .side-a .front .heart .circle,
.card-wrapper.active .card .side-a .front .heart .rect {
background: #ff5c8a !important;
}
.card-wrapper.active .card .side-b .back .heart .circle,
.card-wrapper.active .card .side-b .back .heart .rect {
background: #ff5c8a !important;
}
.card-wrapper:not(.active) .heart {
cursor: pointer;
}
.heart {
font-family: "Satisfy", cursive;
}
.heart .heart-half {
transform: rotate(-45deg) scale(0.8) translateX(23%) translateY(10%);
transform-origin: 140px 140px;
}
.heart .heart-half .circle {
width: 280px;
height: 280px;
background: red;
border-radius: 50%;
transition: background 1s cubic-bezier(0, 0, 0, 0.94);
}
.heart .heart-half .rect {
width: 280px;
height: 280px;
background: red;
margin-top: -140px;
transition: background 1s cubic-bezier(0, 0, 0, 0.94);
}
.heart .title {
position: absolute;
top: 30%;
font-family: "Quicksand", sans-serif;
right: 10%;
font-size: 38px;
transform: rotate(45deg);
color: #ff0a54;
}
.ijustcode {
position: absolute;
top: 124.4444444444px;
right: 0;
height: 100px;
margin: auto;
backface-visibility: hidden;
transform: translateX(50%);
}
.side-b .ijustcode code {
transform: rotateY(-180deg);
}
.side-b .front .ijustcode {
display: none;
}
.hljs-addition, .hljs-attribute, .hljs-meta .hljs-string, .hljs-regexp, .hljs-string {
color: #c379ba;
font-weight: 600;
}
pre code.hljs {
border-radius: 10px;
}
audio {
display: none;
}
/*******
********
Confeti styles
********
*******/
@keyframes confetti-slow {
0% {
transform: translate3d(0, 0, 0) rotateX(0) rotateY(0);
}
100% {
transform: translate3d(25px, 105vh, 0) rotateX(360deg) rotateY(180deg);
}
}
@keyframes confetti-medium {
0% {
transform: translate3d(0, 0, 0) rotateX(0) rotateY(0);
}
100% {
transform: translate3d(100px, 105vh, 0) rotateX(100deg) rotateY(360deg);
}
}
@keyframes confetti-fast {
0% {
transform: translate3d(0, 0, 0) rotateX(0) rotateY(0);
}
100% {
transform: translate3d(-50px, 105vh, 0) rotateX(10deg) rotateY(250deg);
}
}
.container {
/*width: 100vw;
height: 100vh;
background: #f0f0f0;*/
}
.confetti-container {
perspective: 700px;
position: absolute;
overflow: hidden;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: -1;
}
.confetti {
position: absolute;
z-index: 1;
top: -10px;
border-radius: 0%;
}
.confetti-animation-slow {
animation: confetti-slow 2.5s linear 1 forwards;
}
.confetti-animation-medium {
animation: confetti-medium 2s linear 1 forwards;
}
.confetti-animation-fast {
animation: confetti-fast 1.5s linear 1 forwards;
}
#gradient {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100vw;
height: 100vh;
z-index: -2;
opacity: 0;
transition: opacity 1s cubic-bezier(0, 0, 0, 0.94);
}
JavaScript:
This JavaScript powers the whole interaction: it highlights the code using Highlight.js, creates a Confetti class that generates falling confetti pieces with random colors, sizes, and speeds, and starts/stops them when the card is clicked. When you click the card, it toggles the “active” class, plays/pauses the audio, fades the gradient, and shows/hides confetti. It also adds a 3D hover effect by rotating the card based on mouse position. Finally, it uses Granim.js to create a smooth animated gradient in the background. All functions together handle confetti creation, card flipping, sound, hover animation, and background effects.
(function () {
hljs.highlightAll();
var Confetti = function (options) {
var t = this;
t.o = options || {};
//DOM storage
t.doms = {};
//Vars storage
t.vars = {
confettiFrequency: 3, //DEP???
confettiColors: ['#fce18a', '#ff726d', '#b48def', '#f4306d'],
confettiSpeed: ['slow', 'medium', 'fast'],
confetiCount: 0,
confetiLimit: 100,
confettiDestroyTime: 3000, //ms
confettiRenderTime: 60, //ms
confettiSizeRange: [3, 7] };
//classes store
t.classes = {
'confettiContainer': 'confetti-container' };
//callbacks store
t.callbacks = {};
//Init if options are valid
if (t.handleOptions()) {
t.init();
}
};
Confetti.prototype.handleOptions = function () {
var t = this;
if (t.o.target) {
t.doms.target = t.o.target;
} else {
throw 'Confetti.options.target - is not valid DOM element';
return false;
}
if (!!t.o.onstart) {
t.callbacks.onstart = t.o.onstart;
}
if (!!t.o.ondone) {
t.callbacks.ondone = t.o.ondone;
}
return true;
};
Confetti.prototype.setupElements = function () {
var t = this,
containerDOM = document.createElement('div'),
targetPosition = t.doms.target.style.position;
containerDOM.className = t.classes['confettiContainer'];
if (targetPosition != 'relative' || targetPosition != 'absolute') {
t.doms.target.style.position = 'relative';
}
t.doms.target.appendChild(containerDOM);
t.doms.containerDOM = containerDOM;
};
Confetti.prototype.getContainerSize = function () {
var t = this;
return Math.floor(Math.random() * t.vars.confettiSizeRange[0]) + t.vars.confettiSizeRange[1] + 'px';
};
Confetti.prototype.getConfettiColor = function () {
var t = this;
return t.vars.confettiColors[Math.floor(Math.random() * t.vars.confettiColors.length)];
};
Confetti.prototype.getConfettiSpeed = function () {
var t = this;
return t.vars.confettiSpeed[Math.floor(Math.random() * t.vars.confettiSpeed.length)];
};
Confetti.prototype.getConfettiPosition = function () {
var t = this;
return Math.floor(Math.random() * t.doms.target.offsetWidth) + 'px';
};
Confetti.prototype.generateConfetti = function () {var _confettiDOM$classLis, _confettiDOM$classLis2;
var t = this,
confettiDOM = document.createElement('div'),
confettiSize = t.getContainerSize(),
confettiBackground = t.getConfettiColor(),
confettiLeft = t.getConfettiPosition(),
confettiSpeed = t.getConfettiSpeed();
confettiDOM === null || confettiDOM === void 0 ? void 0 : (_confettiDOM$classLis = confettiDOM.classList) === null || _confettiDOM$classLis === void 0 ? void 0 : _confettiDOM$classLis.add('confetti');
confettiDOM === null || confettiDOM === void 0 ? void 0 : (_confettiDOM$classLis2 = confettiDOM.classList) === null || _confettiDOM$classLis2 === void 0 ? void 0 : _confettiDOM$classLis2.add('confetti-animation-' + confettiSpeed);
confettiDOM.style.left = confettiLeft;
confettiDOM.style.width = confettiSize;
confettiDOM.style.height = confettiSize;
confettiDOM.style.backgroundColor = confettiBackground;
confettiDOM.removeTimeout = setTimeout(function () {
confettiDOM.parentNode.removeChild(confettiDOM);
}, t.vars.confettiDestroyTime);
t.doms.containerDOM.appendChild(confettiDOM);
};
Confetti.prototype.renderConfetti = function () {
var t = this;
if (t.callbacks.onstart) {
t.callbacks.onstart();
}
t.confettiInterval = setInterval(function () {
t.vars.confetiCount++;
if (t.vars.confetiCount > t.vars.confetiLimit) {
if (t.callbacks.ondone) {
t.callbacks.ondone();
}
clearInterval(t.confettiInterval);
return false;
} else {
t.generateConfetti();
}
}, t.vars.confettiRenderTime);
};
Confetti.prototype.restart = function (instance) {
var t = this || instance;
t.vars.confetiCount = 0;
t.renderConfetti();
};
Confetti.prototype.start = Confetti.prototype.restart;
Confetti.prototype.stop = function () {
var t = this || instance;
t.vars.confetiCount = t.vars.confetiLimit;
};
Confetti.prototype.init = function () {
var t = this;
t.setupElements();
};
const content = document.querySelector('.content');
const gradient = document.querySelector('#background');
const cardWrapper = document.querySelector('.card-wrapper');
const audio = document.querySelector('audio');
const confetti = new Confetti({ target: content });
cardWrapper.addEventListener('click', () => {
const isActive = cardWrapper.classList.contains('active');
if (isActive) {
cardWrapper.classList.remove('active');
audio.pause();
confetti.stop();
gradient.style.opacity = 0;
} else {
cardWrapper.classList.add('active');
audio.play();
confetti.start();
gradient.style.opacity = 1;
}
});
// https://codesandbox.io/s/3d-hover-effect-hqy6h?file=/src/index.js:0-944
const card = cardWrapper;
const motionMatchMedia = window.matchMedia("(prefers-reduced-motion)");
const THRESHOLD = 30;
function handleHover(e) {
const { clientX, clientY, currentTarget } = e;
const { clientWidth, clientHeight, offsetLeft, offsetTop } = currentTarget;
const horizontal = (clientX - offsetLeft) / clientWidth;
const vertical = (clientY - offsetTop) / clientHeight;
const rotateX = (THRESHOLD / 2 - horizontal * THRESHOLD).toFixed(2);
const rotateY = (vertical * THRESHOLD - THRESHOLD / 2).toFixed(2);
card.style.transform = `perspective(${clientWidth}px) rotateX(${rotateY}deg) rotateY(${rotateX}deg) scale3d(1, 1, 1)`;
}
function resetStyles(e) {
card.style.transform = `perspective(${e.currentTarget.clientWidth}px) rotateX(0deg) rotateY(0deg)`;
}
if (!motionMatchMedia.matches) {
card.addEventListener("mousemove", handleHover);
card.addEventListener("mouseleave", resetStyles);
}
new Granim({
element: '#gradient',
direction: 'radial',
isPausedWhenNotInView: true,
states: {
"default-state": {
gradients: [
['#ff8faf', '#ffe5ed'],
['#f38fff', '#ffe5ed'],
['#ff8f8f', '#ffe5ed']] } } });
})();
In conclusion, building an “I Love You” Heart Card Animation using HTML, CSS, and JavaScript is a fun and creative way to learn interactive web design. With just a few lines of code, we combined structure, styling, and animation to create something expressive and meaningful.
If you run into any problems with your project, worry not. The remedy is just a click away – Download the source code and confront your coding challenges with enthusiasm. Enjoy your coding adventure!
