仅转换 Path2D 的样式

Transform only the styling of a Path2D

在 canvas 2D API 中,我们可以首先使用一个上下文的转换定义子路径,然后仅针对 fill()stroke() 调用更改该上下文的转换,这会对 样式 产生影响,如 fillStylelineWidth 和其他可见属性,但会保留定义的子路径。当我们想在保持相同笔画宽度的同时放大矢量形状时,这非常方便。

这是一个简单的例子,其中只有 lineWidth 受到变量 zoom 转换的影响:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

let zoom = 1;
let speed = 0.1;
requestAnimationFrame(update);

function update() {
  if( zoom >= 10 || zoom <= 0.1 ) speed *= -1;
  zoom += speed;
  draw();
  requestAnimationFrame(update);
}

function draw() {
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.clearRect(0,0,canvas.width,canvas.height);
  // define the subpath at identity matrix
  ctx.beginPath();
  ctx.moveTo(10 ,80);
  ctx.quadraticCurveTo(52.5,10,95,80);
  ctx.quadraticCurveTo(137.5,150,180,80);
  // stroke zoomed
  ctx.setTransform(zoom, 0, 0, zoom, 0, 0);
  ctx.stroke();
}
<canvas id="canvas"></canvas>

使用 Path2D API,我们必须在 ctx.fill(path)ctx.stroke(path) 方法中直接传递此子路径。
这意味着我们不能像以前那样将样式与子路径声明分开:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

let zoom = 1;
let speed = 0.1;
requestAnimationFrame(update);

function update() {
  if( zoom >= 10 || zoom <= 0.1 ) speed *= -1;
  zoom += speed;
  draw();
  requestAnimationFrame(update);
}

function draw() {
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.clearRect(0,0,canvas.width,canvas.height);
  // define the subpath at identity matrix
  // (declared in 'draw' just for the example, would be the same anyway outside)
  const path = new Path2D("M 10 80 Q 52.5 10, 95 80 T 180 80");
  // stroke zoomed
  ctx.setTransform(zoom, 0, 0, zoom, 0, 0);
  ctx.stroke(path);
}
<canvas id="canvas"></canvas>

有没有办法在使用这个方便的 Path2D 时做到这一点 API?

有一种方法可以通过传递 DOMMatrix1 to the Path2D.prototype.addPath 方法来转换 Path2D 对象。

所以我们实际上可以通过传递 Path2d 的转换副本来实现相同的结果:

const transformPath = (path, matrix) => {
  const copy = new Path2D();
  copy.addPath(path, matrix);
  return copy;
};
// ...
ctx.stroke( transformPath( path, {a: 1/zoom, d: 1/zoom } );

但是,您会注意到我们必须使 路径矩阵 相对于 样式 一个。
新的 DOMMatrix API 大大简化了矩阵变换 2,但它使这种方法肯定比 beginPath() 方法更复杂,很遗憾我们不能采取行动在 Path2D 对象本身上,甚至在构造函数上也只有这个 transform 参数,但这是我所知道的唯一方法...

const transformPath = (path, matrix) => {
  const copy = new Path2D();
  copy.addPath(path, matrix);
  return copy;
};

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// define the subpath
const path = new Path2D("M 10 80 Q 52.5 10, 95 80 T 180 80");

let zoom = 1;
let speed = 0.1;
requestAnimationFrame(update);

function update() {
  if( zoom >= 10 || zoom <= 0.1 ) speed *= -1;
  zoom += speed;
  draw();
  requestAnimationFrame(update);
}

function draw() {
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.clearRect(0,0,canvas.width,canvas.height);  
  // zoom the stylings
  ctx.setTransform(zoom, 0, 0, zoom, 0, 0);
  // create our transformed path
  const invertMatrix = {a: 1/zoom, d: 1/zoom};
  ctx.stroke(transformPath(path, invertMatrix));
}
<canvas id="canvas"></canvas>

1.实际上它不需要是一个实际的 DOMMatrix,任何具有其属性的对象都可以
2。我们现在甚至可以在 ctx.setTransform(matrix).

中使用此类对象