Преобразуйте только стиль Path2D - PullRequest
2 голосов
/ 10 января 2020

В Canvas 2D API мы можем сначала определить подпуть, используя преобразование одного контекста, а затем изменить преобразование этого контекста только для вызовов fill() или stroke(), что повлияет на стили как fillStyle, lineWidth и другие видимые свойства, но которые оставят подпуть, как определено. Это очень удобно, когда мы хотим увеличить векторные фигуры, сохраняя при этом одинаковую ширину штриха.

Вот простой пример, когда переменная zoom влияет только на lineWidth преобразование:

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>

С помощью API Path2D мы должны передать этот подпуть непосредственно в методах 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?

1 Ответ

1 голос
/ 10 января 2020

Существует способ преобразовать объект Path2D, передав DOMMatrix 1 методу Path2D.prototype.addPath.

Таким образом, мы действительно можем достичь того же результата, передав преобразованную копию нашего 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).

...