Canvas залипаловка из тик-ток

html+js код генератора залипаловки для вставки в видео для тт или рилсов. Настройка генератора производится путем редактирования констант.




<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>Rotating Rings Game</title>
    <style>
        body {
            background: black;
            display: flex;
            align-items: center;
            justify-content: center;
            height: 100vh;
            margin: 0;
            overflow: hidden;
            touch-action: none;
        }
        canvas {
            background: black;
            display: block;
        }
        #retryButton {
            display: none;
            position: absolute;
            top: 10px;
            padding: 10px 20px;
            font-size: 20px;
            cursor: pointer;
            background: white;
            border: none;
            border-radius: 5px;
        }
    </style>
</head>
<body>
    <canvas id="gameCanvas"></canvas>
    <button id="retryButton">Retry</button>

    <script>
        const canvas = document.getElementById("gameCanvas");
        const ctx = canvas.getContext("2d");

        let CENTER_X, CENTER_Y, BALL_RADIUS, RING_WIDTH, GAP_ANGLE, NUM_RINGS, GRAVITY, TRAIL_LENGTH, BOUNCE_REDUCTION;
        let rings = [];
        let ball;
        let gameOver = false;

        function resizeCanvas() {
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;

            CENTER_X = canvas.width / 2;
            CENTER_Y = canvas.height / 2;

            const minSize = Math.min(canvas.width, canvas.height);
            BALL_RADIUS = minSize * 0.015;
            RING_WIDTH = minSize * 0.005;
            GAP_ANGLE = 40;
            NUM_RINGS = 12;
            GRAVITY = minSize * 0.0003;
            TRAIL_LENGTH = 20;
            BOUNCE_REDUCTION = 0.99;

            resetGame();
        }
        window.addEventListener("resize", resizeCanvas);

        class Ring {
            constructor(index) {
                this.radius = (Math.min(canvas.width, canvas.height) * 0.4) * (index / NUM_RINGS) + BALL_RADIUS * 3;
                this.angle = Math.random() * 360;
                this.speed = 0.5 + index * 0.05;
                this.color = "green";
            }

            update() {
                this.angle = (this.angle + this.speed) % 360;
            }

            draw() {
                ctx.strokeStyle = this.color;
                ctx.lineWidth = RING_WIDTH;
                ctx.beginPath();
                let startAngle = (this.angle + GAP_ANGLE) * (Math.PI / 180);
                let endAngle = (this.angle + 360) * (Math.PI / 180);
                ctx.arc(CENTER_X, CENTER_Y, this.radius, startAngle, endAngle);
                ctx.stroke();
            }

            checkCollision(ball) {
                let distance = Math.hypot(ball.x - CENTER_X, ball.y - CENTER_Y);
                if (distance >= this.radius - BALL_RADIUS && distance <= this.radius + BALL_RADIUS) {
                    let angleToBall = Math.atan2(ball.y - CENTER_Y, ball.x - CENTER_X) * (180 / Math.PI);
                    if (angleToBall < 0) angleToBall += 360;

                    if (angleToBall >= this.angle && angleToBall <= this.angle + GAP_ANGLE) {
                        return "pass";
                    } else {
                        return "hit";
                    }
                }
                return "none";
            }

            reflectBall(ball) {
                let angle = Math.atan2(ball.y - CENTER_Y, ball.x - CENTER_X);
                let speed = Math.hypot(ball.vx, ball.vy) * BOUNCE_REDUCTION;

                let normal = angle + Math.PI / 2;
                let tangent = Math.atan2(ball.vy, ball.vx);
                let reflectAngle = 2 * normal - tangent;

                ball.vx = speed * Math.cos(reflectAngle);
                ball.vy = speed * Math.sin(reflectAngle);
                this.changeColor();
            }

            changeColor() {
                const colors = ["red", "blue", "yellow", "cyan", "magenta", "lime"];
                this.color = colors[Math.floor(Math.random() * colors.length)];
            }
        }

        function resetGame() {
            rings = [];
            for (let i = 0; i < NUM_RINGS; i++) {
                rings.push(new Ring(i));
            }

            ball = {
                x: CENTER_X,
                y: CENTER_Y,
                vx: (Math.random() - 0.5) * 5,
                vy: (Math.random() - 0.5) * 5,
                trail: []
            };

            gameOver = false;
            document.getElementById("retryButton").style.display = "none";
            gameLoop();
        }

        function updateBall() {
            if (gameOver) return;

            ball.x += ball.vx;
            ball.y += ball.vy;
            ball.vy += GRAVITY;

            ball.trail.push({ x: ball.x, y: ball.y });
            if (ball.trail.length > TRAIL_LENGTH) {
                ball.trail.shift();
            }

            for (let i = rings.length - 1; i >= 0; i--) {
                let ring = rings[i];
                let collision = ring.checkCollision(ball);

                if (collision === "pass") {
                    rings.splice(i, 1);
                } else if (collision === "hit") {
                    ring.reflectBall(ball);
                }
            }

            if (ball.y + BALL_RADIUS >= canvas.height) {
                ball.y = canvas.height - BALL_RADIUS;
                ball.vy *= -BOUNCE_REDUCTION;
            }
        }

        function drawBall() {
            for (let i = 0; i < ball.trail.length; i++) {
                let alpha = i / ball.trail.length;
                ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`;
                let size = BALL_RADIUS * (i / ball.trail.length);
                ctx.beginPath();
                ctx.arc(ball.trail[i].x, ball.trail[i].y, size, 0, Math.PI * 2);
                ctx.fill();
            }

            ctx.fillStyle = "white";
            ctx.beginPath();
            ctx.arc(ball.x, ball.y, BALL_RADIUS, 0, Math.PI * 2);
            ctx.fill();
        }

        function gameLoop() {
            ctx.fillStyle = "black";
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            if (rings.length === 0) {
                ctx.fillStyle = "white";
                ctx.font = `${Math.min(canvas.width, canvas.height) * 0.05}px Arial`;
                ctx.fillText("You Win!", CENTER_X - 80, CENTER_Y);
                document.getElementById("retryButton").style.display = "block";
                gameOver = true;
                return;
            }

            for (let ring of rings) {
                ring.update();
                ring.draw();
            }

            updateBall();
            drawBall();

            requestAnimationFrame(gameLoop);
        }

        document.getElementById("retryButton").addEventListener("click", resetGame);

        resizeCanvas();
    </script>
</body>
</html>


image


URL: http://saxar.pp.ua/articles/13