>_htmlhub
library/celebration/envato-bouncing-balloons

Bouncing SVG Balloons

Live previewPerformance varies by device
envato-bouncing-balloons.html

No account needed to copy or download. Check dependencies and thelicense status before publishing.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Bouncing SVG Balloons</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #f8fafc;
            font-family: sans-serif;
            cursor: pointer;
            /* Interaction hint */
        }
        #container {
            width: 100vw;
            height: 100vh;
            position: relative;
        }
        .balloon {
            position: absolute;
            transform-origin: bottom center;
            will-change: transform, top, left;
        }
        .string {
            stroke: #cbd5e1;
            stroke-width: 2;
            fill: none;
        }
    </style>
</head>
<body>
    <div id="container"></div>
    <script>
        const container = document.getElementById('container');
        const colors = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#8b5cf6', '#ec4899'];
        let balloons = [];
        const config = {
            count: 15,
            gravity: 0.15,
            bounce: -0.6,
            friction: 0.98
        };
        class Balloon {
            constructor(x, y, isClick = false) {
                this.x = x || Math.random() * window.innerWidth;
                this.y = y || Math.random() * (window.innerHeight / 2) - window.innerHeight; // Start offscreen top
                this.vx = (Math.random() - 0.5) * 4;
                this.vy = isClick ? -5 - Math.random() * 5 : 0;
                this.radiusX = 30 + Math.random() * 15;
                this.radiusY = this.radiusX * 1.3;
                this.color = colors[Math.floor(Math.random() * colors.length)];
                this.element = this.createSVG();
                container.appendChild(this.element);
            }
            createSVG() {
                const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
                const width = this.radiusX * 2.5;
                const height = this.radiusY * 2.5 + 60; // Extra for string
                svg.setAttribute('width', width);
                svg.setAttribute('height', height);
                svg.setAttribute('class', 'balloon');
                // Definitions for gradient
                const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
                const gradient = document.createElementNS("http://www.w3.org/2000/svg", "radialGradient");
                const id = 'grad-' + Math.random().toString(36).substr(2, 9);
                gradient.setAttribute('id', id);
                gradient.setAttribute('cx', '30%');
                gradient.setAttribute('cy', '30%');
                gradient.setAttribute('r', '50%');
                const stop1 = document.createElementNS("http://www.w3.org/2000/svg", "stop");
                stop1.setAttribute('offset', '0%');
                stop1.setAttribute('stop-color', '#ffffff');
                stop1.setAttribute('stop-opacity', '0.6');
                const stop2 = document.createElementNS("http://www.w3.org/2000/svg", "stop");
                stop2.setAttribute('offset', '100%');
                stop2.setAttribute('stop-color', this.color);
                gradient.appendChild(stop1);
                gradient.appendChild(stop2);
                defs.appendChild(gradient);
                svg.appendChild(defs);
                // String
                const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
                path.setAttribute('d', `M ${width / 2} ${this.radiusY * 2 + 5} Q ${width / 2 + 10} ${this.radiusY * 2 + 30} ${width / 2 - 5} ${height}`);
                path.setAttribute('class', 'string');
                svg.appendChild(path);
                // Tie
                const tie = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
                tie.setAttribute('points', `${width / 2},${this.radiusY * 2} ${width / 2 - 6},${this.radiusY * 2 + 8} ${width / 2 + 6},${this.radiusY * 2 + 8}`);
                tie.setAttribute('fill', this.color);
                svg.appendChild(tie);
                // Balloon Body
                const ellipse = document.createElementNS("http://www.w3.org/2000/svg", "ellipse");
                ellipse.setAttribute('cx', width / 2);
                ellipse.setAttribute('cy', this.radiusY);
                ellipse.setAttribute('rx', this.radiusX);
                ellipse.setAttribute('ry', this.radiusY);
                ellipse.setAttribute('fill', `url(#${id})`);
                svg.appendChild(ellipse);
                return svg;
            }
            update() {
                this.vy += config.gravity;
                this.vx *= config.friction;
                this.x += this.vx;
                this.y += this.vy;
                const ground = window.innerHeight - (this.radiusY * 2.5 + 60);
                // Floor collision
                if (this.y > ground) {
                    this.y = ground;
                    this.vy *= config.bounce;
                    this.vx *= 0.9; // Extra floor friction
                }
                // Wall collision
                if (this.x < 0) {
                    this.x = 0;
                    this.vx *= -1;
                } else if (this.x > window.innerWidth - this.radiusX * 2.5) {
                    this.x = window.innerWidth - this.radiusX * 2.5;
                    this.vx *= -1;
                }
                // Squeeze effect based on velocity
                const scaleY = 1 - Math.min(Math.abs(this.vy) * 0.02, 0.3);
                const scaleX = 1 + Math.min(Math.abs(this.vy) * 0.01, 0.15);
                const rotation = this.vx * 2;
                this.element.style.transform = `translate(${this.x}px, ${this.y}px) rotate(${rotation}deg) scale(${scaleX}, ${scaleY})`;
            }
        }
        function init() {
            container.innerHTML = '';
            balloons = [];
            for (let i = 0; i < config.count; i++) {
                setTimeout(() => {
                    balloons.push(new Balloon());
                }, i * 200); // Staggered spawn
            }
        }
        function animate() {
            for (let i = 0; i < balloons.length; i++) {
                balloons[i].update();
            }
            requestAnimationFrame(animate);
        }
        window.addEventListener('click', (e) => {
            // Spawn 3 balloons on click
            for (let i = 0; i < 3; i++) {
                balloons.push(new Balloon(e.clientX - 30, window.innerHeight, true));
            }
            // Keep array size manageable
            if (balloons.length > config.count + 15) {
                const old = balloons.shift();
                old.element.remove();
            }
        });
        init();
        animate();
    </script>
</body>
</html>

About this animation

Gravity-affected SVG balloons that bounce and settle interactively on the bottom of the screen.

Source Code
Copied to clipboard