使用 canvas 构建球金字塔

Build a pyramid of balls using the canvas

我在 canvas 上复制下面的金字塔时遇到困难。

关于如何在每条新线上绘制一个新球的数学部分,我正在苦苦挣扎。到目前为止,这是我的代码。

<canvas id="testCanvas" width="300" height="300" style="border:1px solid #d3d3d3;"></canvas>

<script>
    // Access canvas element and its context
    const canvas = document.getElementById('testCanvas');
    const context = canvas.getContext("2d");

    const x = canvas.width;
    const y = canvas.height;
    const radius = 10;
    const diamater = radius * 2;
    const numOfRows = canvas.width / diamater;

    function ball(x, y) {
        context.arc(x, y, radius, 0, 2 * Math.PI, true);
        context.fillStyle = "#FF0000"; // red
        context.fill();
    }

    function draw() {
        for (let i = 0; i < numOfRows; i++) {
            for (let j = 0; j < i + 1; j++) {
                ball(
                    //Pos X
                    (x / 2),
                    //Pos Y
                    diamater * (i + 1)
                );
            }
        }
        ball(x / 2, y);

        context.restore();
    }

    draw();

</script>

我一直被这个问题困扰了一段时间。感谢您提供的任何帮助。 谢谢。

解决这个问题的方法是一次将其分解为一个步骤。

  1. 在第 (1) 行画 1 个圆
  2. 在第 (2) 行画 2 个圆圈
  3. 在第 (3) 行画 3 个圆圈

等等...

然后你必须弄清楚每个圆圈的绘制位置。那你也可以分解成步骤。

第 1 行第 1 个圆在中心(宽度)

第2行第1圆圆心减去直径

第2行第2圆在圆心加上直径

等等。

这样做你会找到一个模式转换成 2 个 for 循环。

像这样:

//1st row 1st circle
ball(w/2,radius * 1, red);

//2nd row 1st circle
ball(w/2 - radius,radius * 3, blue); 
//2nd row 2nd circle
ball(w/2 + radius,radius * 3, blue); 

下面的代码显示了如何绘制每个球的每个步骤。我也做了一些更正来处理 numberOfRows。

const canvas = document.getElementById('testCanvas');
const context = canvas.getContext("2d");

const w = canvas.width;
const h = canvas.height;
const radius = 10;
const diamater = radius * 2;
const numOfRows = Math.min(h / diamater, w / diamater);
const red = "#FF0000";
const blue = "#0000FF";

var k = 1;

function ball(x, y, color) {
  setTimeout(function() {
    context.beginPath();
    context.arc(x, y, radius, 0, 2 * Math.PI, true);
    context.fillStyle = color;
    context.fill();
  }, (k++) * 250);
}

for (var i = 1; i <= numOfRows; i++) {
  for (var j = 1; j <= i; j++) {
    var y = (i * radius * 2) - radius;
    var x = (w / 2) - ((i * radius) + radius) + (j * diamater);
    ball(x, y, i % 2 ? red : blue);
  }
}
<canvas id="testCanvas" 
        width="300" height="180" 
        style="border:1px solid #d3d3d3;"></canvas>

我注意到圆圈没有接触。我不确定您是否需要或希望他们这样做,但由于这是一个有趣的问题,所以我创建了这个答案。

堆叠圆之间的距离。

可以使用直角三角形计算行间距,如下图所示

其中 R 是圆的半径,D 是行间距。

   D = ((R + R) ** 2 - R ** 2) ** 0.5;

这样我们就可以得到给定半径可以容纳的行数为

  S = (H - R * 2) / D;

其中 H 是 canvas 的高度,S 是行数。

例子

给定的半径使尽可能多的行适合给定的 canvas 高度。

const ctx = canvas.getContext("2d");
const W = canvas.width, H = canvas.height, CENTER = W / 2;
const cols = ["#E80", "#0B0"];
draw();
function fillPath(path, x, y, color) {
    ctx.fillStyle = color;
    ctx.setTransform(1, 0, 0, 1, x, y);
    ctx.fill(path);
}
function draw() {
    const R = 10;
    const D = ((R * 2) ** 2 - R ** 2) ** 0.5;
    const S = (H - R * 2) / D | 0;
    const TOP = R + (H - (R * 2 + D * S)) / 2;  // center horizontal
    const circle = new Path2D();
    circle.arc(0, 0, R, 0, Math.PI * 2);
    var y = 0, x;
    while (y <= S) {
        x = 0;
        const LEFT = CENTER - (y * R);
        while (x <= y) {
            fillPath(circle, LEFT + (x++) * R * 2, TOP + y * D, cols[y % 2]);  
        }
        y ++;
    }
}
canvas {
  border:1px solid #ddd;
}
<canvas id="canvas"  width="300" height="180"></canvas>

适合 n 行堆叠圆的半径

或者如果您有要适合的高度 H 和行数 S。如下图所示。

我们想在给定 HS 的情况下找到 R 我们重新排列 H 并用

求解结果二次方程
ss = S * S - 2 * S + 1;
a = 4 / ss;
b = -4 * H / ss;
c = H * H / ss;
R = (-b-(b*b - 4 * a * c) ** 0.5) / (2 * a); // the radius

例子

给定行数(数字输入)计算适合该行数的半径

const ctx = canvas.getContext("2d");
const W = canvas.width, H = canvas.height, CENTER = W / 2;
rowsIn.addEventListener("input", draw)
const cols = ["#DD0", "#0A0"];
draw();
function fillPath(path, x, y, color) {
    ctx.fillStyle = color;
    ctx.setTransform(1, 0, 0, 1, x, y);
    ctx.fill(path);
}
function draw() {
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.clearRect(0,0,W,H);
    const S = Number(rowsIn.value);
    const ss = S * S - 2 * S + 1;
    const a = 4 / ss - 3, b = -4 * H / ss, c = H * H / ss;
    const R = (- b - ((b * b - 4 * a * c) ** 0.5)) / (2 * a); // the radius
    const TOP = R;
    const D = ((R * 2) ** 2 - R ** 2) ** 0.5;
    //const S = (H - R * 2) / D;
    const circle = new Path2D();
    circle.arc(0, 0, R, 0, Math.PI * 2);
    var y = 0, x;
    while (y < S) {
        x = 0;
        const LEFT = CENTER - (y * R);
        while (x <= y) {
            fillPath(circle, LEFT + (x++) * R * 2, TOP + y * D, cols[y % 2]);  
        }
        y ++;
    }
}
canvas {
  border:1px solid #ddd;
}
<canvas id="canvas"  width="300" height="180"></canvas>
<input type="number" id="rowsIn"  min="3" max="12" value="3">Rows