Для выравнивания макета я использовал некоторые функции CSS flexbox, в рамках этих функций их можно выровнять всеми возможными способами.
Подробнее о flexbox здесь: https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox и посмотрите мой пример для быстрого старта.
const ctx = document.getElementById("Canvas").getContext("2d");
const ctx2 = document.getElementById("Canvas2").getContext("2d");
const ctx3 = document.getElementById("Canvas3").getContext("2d");
const ctx4 = document.getElementById("Canvas4").getContext("2d");
const containerR = 80;
const size = containerR * 2
ctx.canvas.width = ctx.canvas.height = size;
ctx2.canvas.width = ctx2.canvas.height = size;
ctx3.canvas.width = ctx3.canvas.height = size;
ctx4.canvas.width = ctx4.canvas.height = size;
ctx.globalAlpha = 0.6;
ctx2.globalAlpha = 0.8;
ctx3.globalAlpha = 0.8;
ctx4.globalAlpha = 0.8;
const getBall = (x, y, dx, dy, r, color) => ({
x,
y,
dx,
dy,
r,
color
});
const balls = [
getBall(size / 2, size - 30, -0.1, -0.1, 4, "Green"),
getBall(size / 3, size - 50, 0.1, 0.1, 4, "Green"),
getBall(size / 4, size - 60, -0.1, 0.1, 4, "Green"),
getBall(size / 2, size / 5, 0.1, 0.1, 4, "Green"),
getBall(size / 2, size / 5, 0.1, -0.1, 4, "Green"),
];
const drawBall = (ball) => {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2, false);
ctx.fillStyle = ball.collider ? "red" : ball.color;
ctx.fill();
ctx.closePath();
ctx2.beginPath();
ctx2.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2, false);
ctx2.fillStyle = ball.collider ? "red" : ball.color;
ctx2.fill();
ctx2.closePath();
ctx3.beginPath();
ctx3.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2, false);
ctx3.fillStyle = ball.collider ? "red" : ball.color;
ctx3.fill();
ctx3.closePath();
ctx4.beginPath();
ctx4.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2, false);
ctx4.fillStyle = ball.collider ? "red" : ball.color;
ctx4.fill();
ctx4.closePath();
}
const updatePos = (ball) => {
ball.x += ball.dx;
ball.y += ball.dy;
const dx = ball.x - containerR;
const dy = ball.y - containerR;
if (Math.sqrt(dx * dx + dy * dy) >= containerR - ball.r) {
const v = Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
const angleToCollisionPoint = Math.atan2(-dy, dx);
const oldAngle = Math.atan2(-ball.dy, ball.dx);
const newAngle = 2 * angleToCollisionPoint - oldAngle;
ball.dx = -v * Math.cos(newAngle);
ball.dy = v * Math.sin(newAngle);
}
}
const collides = (a, b) => (Math.hypot(Math.abs(a.x - b.x), Math.abs(a.y - b.y)) < (a.r + b.r));
function engine() {
//console.clear(); // Clear console test messages
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx2.clearRect(0, 0, ctx2.canvas.width, ctx2.canvas.height);
ctx3.clearRect(0, 0, ctx3.canvas.width, ctx3.canvas.height);
ctx4.clearRect(0, 0, ctx4.canvas.width, ctx4.canvas.height);
balls.forEach((a, ai) => {
a.collider = undefined;
balls.forEach((b, bi) => {
if (bi === ai) return; // Don't look at self
if (collides(a, b)) a.collider = b; // Store the colliding B ball
});
updatePos(a);
drawBall(a);
});
requestAnimationFrame(engine);
}
engine();
.row {
display: flex;
}
.container {
display: flex;
flex-direction: column;
}
#Canvas, #Canvas2, #Canvas3, #Canvas4 {
background: #eee;
border-radius: 50%;
border: solid 4px #000;
margin: 10px;
}
.canvas-holder {
display: flex;
flex-direction: column;
align-items: center;
}
<div class="container">
<div class="row">
<div class="canvas-holder">
<canvas id="Canvas"></canvas>
<span>Circle one</span>
</div>
<div class="canvas-holder">
<canvas id="Canvas2"></canvas>
<span>Circle two</span>
</div>
</div>
<div class="row">
<div class="canvas-holder">
<canvas id="Canvas3"></canvas>
<span>Circle tree</span>
</div>
<div class="canvas-holder">
<canvas id="Canvas4"></canvas>
<span>Circle four</span>
</div>
</div>
</div>