Amazing JavaScript - Heart Animation
javascript

Amazing JavaScript - Heart Animation

Heart Animation is a captivating coding project that brings a vibrant heart to life with smooth, rhythmic beats.This animation showcases the power of JavaScript to create visually stunning effects.

amazing-javascript-heart-animation.javascript
1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <title>Particle Heart</title>
6    <style>
7        html, body {
8            margin: 0;
9            padding: 0;
10            width: 100%;
11            height: 100%;
12            background: #05010a;
13            overflow: hidden;
14        }
15        canvas {
16            display: block;
17        }
18        #tap-hint {
19            position: absolute;
20            bottom: 30px;
21            width: 100%;
22            text-align: center;
23            color: rgba(255,255,255,0.35);
24            font-family: -apple-system, "Segoe UI", sans-serif;
25            font-size: 14px;
26            letter-spacing: 1px;
27            pointer-events: none;
28            animation: fade 3s ease-in-out infinite;
29        }
30        @keyframes fade {
31            0%, 100% { opacity: 0.15; }
32            50% { opacity: 0.5; }
33        }
34    </style>
35</head>
36<body>
37<canvas id="c"></canvas>
38<div id="tap-hint">tap for a burst ✨</div>
39<script>
40    const canvas = document.getElementById('c');
41    const ctx = canvas.getContext('2d');
42    let W, H, DPR;
43
44    function resize() {
45        DPR = Math.min(window.devicePixelRatio || 1, 2);
46        W = window.innerWidth;
47        H = window.innerHeight;
48        canvas.width = W * DPR;
49        canvas.height = H * DPR;
50        canvas.style.width = W + 'px';
51        canvas.style.height = H + 'px';
52        ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
53    }
54    resize();
55    window.addEventListener('resize', resize);
56
57    // ---- Heart curve sampling ----
58    function heartPoint(t) {
59        // classic parametric heart, t in [0, 2*PI]
60        const x = 16 * Math.pow(Math.sin(t), 3);
61        const y = -(13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t));
62        return { x, y };
63    }
64
65    function makeParticles(count, scale, cx, cy) {
66        const pts = [];
67        for (let i = 0; i < count; i++) {
68            const t = (i / count) * Math.PI * 2;
69            const base = heartPoint(t);
70            pts.push({
71                // target position on the heart outline
72                tx: cx + base.x * scale,
73                ty: cy + base.y * scale,
74                // current position starts scattered
75                x: cx + (Math.random() - 0.5) * W * 1.4,
76                y: cy + (Math.random() - 0.5) * H * 1.4,
77                // slight per-particle jitter offset so heart looks alive, not rigid
78                jitterA: Math.random() * Math.PI * 2,
79                jitterSpeed: 0.5 + Math.random() * 1.2,
80                jitterAmp: 2 + Math.random() * 6,
81                size: Math.random() * 2 + 1.2,
82                hueShift: Math.random() * 20 - 10,
83                settleSpeed: 0.03 + Math.random() * 0.03,
84                vx: 0,
85                vy: 0
86            });
87        }
88        return pts;
89    }
90
91    let particles = [];
92    let bursts = []; // temporary burst particles
93
94    function initHeart() {
95        const cx = W / 2;
96        const cy = H / 2;
97        const scale = Math.min(W, H) / 34;
98        const count = Math.floor((W * H) / 1400);
99        particles = makeParticles(Math.max(600, Math.min(count, 2600)), scale, cx, cy);
100    }
101    initHeart();
102
103    function addBurst(x, y) {
104        const n = 60;
105        for (let i = 0; i < n; i++) {
106            const angle = Math.random() * Math.PI * 2;
107            const speed = 2 + Math.random() * 6;
108            bursts.push({
109                x, y,
110                vx: Math.cos(angle) * speed,
111                vy: Math.sin(angle) * speed,
112                life: 1,
113                decay: 0.012 + Math.random() * 0.02,
114                size: Math.random() * 2.5 + 1,
115                hue: 320 + Math.random() * 40
116            });
117        }
118    }
119
120    canvas.addEventListener('click', (e) => addBurst(e.clientX, e.clientY));
121    canvas.addEventListener('touchstart', (e) => {
122        for (const t of e.touches) addBurst(t.clientX, t.clientY);
123    }, { passive: true });
124
125    let frame = 0;
126    let pulsePhase = 0;
127
128    function draw() {
129        frame++;
130        pulsePhase += 0.045;
131
132        // trailing fade — soft glow trails behind particles
133        ctx.fillStyle = 'rgba(5, 1, 10, 0.18)';
134        ctx.fillRect(0, 0, W, H);
135
136        const pulse = 1 + Math.sin(pulsePhase) * 0.035;
137        const cx = W / 2;
138        const cy = H / 2;
139
140        ctx.globalCompositeOperation = 'lighter';
141
142        for (const p of particles) {
143            // ease toward target heart position
144            const dx = (p.tx - cx) * pulse + cx - p.x;
145            const dy = (p.ty - cy) * pulse + cy - p.y;
146            p.vx += dx * p.settleSpeed * 0.02;
147            p.vy += dy * p.settleSpeed * 0.02;
148            p.vx *= 0.86;
149            p.vy *= 0.86;
150            p.x += p.vx;
151            p.y += p.vy;
152
153            // gentle floating jitter once settled
154            const j = Math.sin(frame * 0.02 * p.jitterSpeed + p.jitterA) * p.jitterAmp * 0.15;
155
156            const hue = 330 + p.hueShift + Math.sin(frame * 0.01 + p.jitterA) * 8;
157            const light = 55 + Math.sin(frame * 0.03 + p.jitterA) * 15;
158
159            ctx.beginPath();
160            ctx.fillStyle = `hsla(${hue}, 90%, ${light}%, 0.85)`;
161            ctx.arc(p.x + j, p.y + j, p.size, 0, Math.PI * 2);
162            ctx.fill();
163        }
164
165        // burst particles
166        for (let i = bursts.length - 1; i >= 0; i--) {
167            const b = bursts[i];
168            b.x += b.vx;
169            b.y += b.vy;
170            b.vx *= 0.96;
171            b.vy *= 0.96;
172            b.life -= b.decay;
173            if (b.life <= 0) {
174                bursts.splice(i, 1);
175                continue;
176            }
177            ctx.beginPath();
178            ctx.fillStyle = `hsla(${b.hue}, 95%, 65%, ${b.life})`;
179            ctx.arc(b.x, b.y, b.size * b.life * 2, 0, Math.PI * 2);
180            ctx.fill();
181        }
182
183        // soft central glow
184        const grad = ctx.createRadialGradient(cx, cy, 0, cx, cy, Math.min(W, H) * 0.45);
185        grad.addColorStop(0, 'rgba(255, 90, 160, 0.10)');
186        grad.addColorStop(1, 'rgba(255, 90, 160, 0)');
187        ctx.fillStyle = grad;
188        ctx.fillRect(0, 0, W, H);
189
190        ctx.globalCompositeOperation = 'source-over';
191
192        requestAnimationFrame(draw);
193    }
194
195    // initial black fill so trail effect starts clean
196    ctx.fillStyle = '#05010a';
197    ctx.fillRect(0, 0, W, H);
198
199    // auto-bursts occasionally for extra sparkle, TikTok-style
200    setInterval(() => {
201        const cx = W / 2 + (Math.random() - 0.5) * W * 0.3;
202        const cy = H / 2 + (Math.random() - 0.5) * H * 0.3;
203        addBurst(cx, cy);
204    }, 1800);
205
206    draw();
207</script>
208</body>
209</html>

Ready to Level Up?

Stop guessing what to learn next. Get the structured path that separates senior engineers from everyone else.

Grab Your Free Roadmap