为什么需要多个 rotate() 来将每个数字放在正确的位置? - Canvas 时钟编号

Why are multiple rotate() needed to place every number on the right spot? - Canvas Clock Numbers

关注this tutorial which shows how to make an analog clock using HTML canvas, I've had a hard time understanding what is going on when placing numbers on the clock face.

代码为here,以下是我想请教的部分

function drawNumbers(ctx, radius) {
  var ang;
  var num;
  ctx.font = radius * 0.15 + "px arial";
  ctx.textBaseline = "middle";
  ctx.textAlign = "center";

  for(num = 1; num < 13; num++){
    ang = num * Math.PI / 6;
    ctx.rotate(ang);
    ctx.translate(0, -radius * 0.85);
    ctx.rotate(-ang);
    ctx.fillText(num.toString(), 0, 0);
    ctx.rotate(ang);
    ctx.translate(0, radius * 0.85);
    ctx.rotate(-ang);
  }
}

在 for 循环中,第一个 ctx.rotate(ang) 将数字设置在它应该位于的位置。

下一次旋转 ctx.rotate(-ang) 会使数字恢复直立状态,因为它是倾斜的。 (虽然我不知道为什么它会这样而不把数字放回第一位。)

然后,ctx.fillText(…)显示数字后,它似乎又做了同样的事情。

为什么需要这两个rotate()?它们的工作方式与上层的不同吗?如果做,怎么做?

这段代码试图做的是回到它之前的位置,即 canvas 的中心。

将上下文想象成一张 sheet 可以旋转和移动的纸 (translate),上面有固定的笔。

  • 首先,他们确实旋转了那张 sheet 纸,以便沿着所需的方向描绘一条垂直线。
  • 然后他们将纸的sheet垂直移动,使笔处于正确的位置。不过这里的纸sheet还是旋转的,所以如果从这里横着画,画出来的会是斜的。
  • 所以它们确实以另一种方式再次旋转以使文本处于正确的角度。
  • 他们绘制文本。
  • 现在他们想回到点 1 以便能够绘制下一个刻度。为此,他们采用相同的路线,但采用另一种方式:将纸 sheet 旋转回所需的角度,以便它们可以垂直移动到中心。
  • 垂直移动到中心
  • 最后向后旋转,使纸的 sheet 处于下一个刻度的原始方向。

但是你不应该这样做。 rotate()可能最终会出现舍入问题,所以rotate(angle); rotate(-angle)不能回到初始方向,但是对于一些轻微旋转的状态,这对您的应用程序来说可能是灾难性的,因为现在当您尝试绘制像素完美的线条时,您将无法并且会破坏应用程序的整体性能。

而是使用绝对setTransform方法回到原来的位置:

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var radius = canvas.height / 2;
radius = radius * 0.90
drawNumbers(ctx, radius);

function drawNumbers(ctx, radius) {
  var ang;
  var num;
  ctx.font = radius * 0.15 + "px arial";
  ctx.textBaseline = "middle";
  ctx.textAlign = "center";
  for(num = 1; num < 13; num++){
    ang = num * Math.PI / 6;
    // go (back) to center
    ctx.setTransform(1, 0, 0, 1, radius, radius);
    ctx.rotate(ang);
    ctx.translate(0, -radius * 0.85);
    ctx.rotate(-ang);
    ctx.fillText(num.toString(), 0, 0);
  }
  // reset to identity matrix;
  ctx.setTransform(1, 0, 0, 1, 0, 0); 
}
canvas {
  background-color: white;
}
<canvas id="canvas" width="400" height="400">
</canvas>

这是另一个不使用旋转的实现。
相反,我用一点三角函数计算 x, y

起始角度为var ang = Math.PI;
然后在循环中我们减少它 ang -= Math.PI / 6;

一旦知道公式,计算位置就很容易了:

let x = radius * Math.sin(ang)
let y = radius * Math.cos(ang)

下面是一个功能齐全的示例

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.font = "16px arial";
ctx.textAlign = "center";

var radius = 60
var ang = Math.PI;

for (let num = 1; num < 13; num++) {
  ang -= Math.PI / 6;
  let x = radius * Math.sin(ang)
  let y = radius * Math.cos(ang)

  ctx.fillText(num.toString(), x, y);
  ctx.beginPath()
  ctx.arc(x, y - 6, 12, 0, 2 * Math.PI);
  ctx.stroke();  
  ctx.beginPath()
  ctx.arc(x, y - 6, 45, -ang-2,-ang);
  ctx.stroke();  
}
<canvas id="canvas" width="160" height="160"></canvas>

我个人从来不喜欢使用旋转,因为小的静态 canvas 图像可能没问题,但是当我们转向具有多个对象的更复杂的动画时,当我必须调试多次旋转时很快就会变得很痛苦,很难跟上。