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>
URL:
http://saxar.pp.ua/articles/13