缩放后在画布上获取相对鼠标位置

时间:2017-08-24 02:36:49

标签: javascript canvas html5-canvas

问题:我正在使用HTML画布。我的画布有一个背景图像,多个人可以实时绘制(通过socket.io),但如果放大则绘制中断。

原因:要计算从哪里开始和结束一行,我将捕获时的输入规范化为0到1(包括0和1),如下所示:

// Pseudocode
line.x = mousePosition.x / canvas.width;    
line.y = mousePosition.y / canvas.height;

因此,canvas可以是任何大小和任何位置。

要实现缩放滚动功能,我只需translate根据当前鼠标位置,scale画布按因子2,然后translate返回负值当前鼠标位置(建议here)。

问题出在哪里

当我缩放时,画布似乎没有原始尺寸的概念。

例如,假设我有1000px的方形画布。使用上面标准化的xy,左上角为0, 0,右下角为1, 1

然后我通过缩放2倍缩放到中心。我希望我的新左上角是0.5, 0.5而我的右下角是0.75, 0.75,但事实并非如此。即使我放大,左上角仍为0, 0,右下角为1, 1

结果是,当我放大并绘制时,线条会出现在我们根本没有放大的位置。如果我放大到中间并在左上角“画了”,我会看到什么,直到我滚动出来看到这条线实际上是在原来的左上角绘制的。

我需要知道的事情:当缩放时,是否有办法了解新原点相对于未缩放画布的内容,或者隐藏了多少画布?这些中的任何一个都可以放大并绘制并使其正确跟踪。

如果我完全不在这里,并且有更好的方法来解决这个问题,我会全力以赴。如果您需要其他信息,我会尽我所能。

2 个答案:

答案 0 :(得分:0)

使用HTMLElement.prototype.getBoundingClientRect()获取DOM中画布的显示大小和位置。从显示的大小和原始大小,计算画布的比例。

示例:

canvas.addEventListener("click", function (event) {
    var b = canvas.getBoundingClientRect();
    var scale = canvas.width / parseFloat(b.width);
    var x = (event.clientX - b.left) * scale;
    var y = (event.clientY - b.top) * scale;
    // Marks mouse position
    var ctx = canvas.getContext("2d");
    ctx.beginPath();
    ctx.arc(x, y, 10, 0, 2 * Math.PI);
    ctx.stroke();
});

答案 1 :(得分:0)

我不清楚您所说的“放大”是什么意思。

缩放=

  • 使画布的尺寸不同了吗?

  • 更改了画布上的变换

  • 使用了CSS转换吗?

  • 使用CSS缩放了吗?

我将假设它是transform在画布上,在这种情况下,它就像

function getElementRelativeMousePosition(e) {
  return [e.offsetX, e.offsetY];
}

function getCanvasRelativeMousePosition(e) {
  const pos = getElementRelativeMousePosition(e);
  pos[0] = pos[0] * ctx.canvas.width / ctx.canvas.clientWidth;
  pos[1] = pos[1] * ctx.canvas.height / ctx.canvas.clientHeight;
  return pos;
}

function getComputedMousePosition(e) {
  const pos = getCanvasRelativeMousePosition(e);
  const p = new DOMPoint(...pos);
  const point = inverseOriginTransform.transformPoint(p);
  return [point.x, point.y];
}

inverseOriginTransform与您用来缩放和滚动画布内容的任何变换相反。

const settings = {
  zoom: 1,
  xoffset: 0,
  yoffset: 0,
};

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const lines = [
   [[100, 10], [200, 30]],
   [[50, 50], [100, 30]],
];
let newStart;
let newEnd;
let originTransform = new DOMMatrix();
let inverseOriginTransform = new DOMMatrix();

function setZoomAndOffsetTransform() {
  originTransform = new DOMMatrix();
  originTransform.translateSelf(settings.xoffset, settings.yoffset);
  originTransform.scaleSelf(settings.zoom, settings.zoom);
  inverseOriginTransform = originTransform.inverse();
} 

const ui = document.querySelector('#ui')
addSlider(settings, 'zoom', ui, 0.25, 3, draw);
addSlider(settings, 'xoffset', ui, -100, +100, draw);
addSlider(settings, 'yoffset', ui, -100, +100, draw);

draw();

function updateAndDraw() {
  draw();
}

function getElementRelativeMousePosition(e) {
  return [e.offsetX, e.offsetY];
}

function getCanvasRelativeMousePosition(e) {
  const pos = getElementRelativeMousePosition(e);
  pos[0] = pos[0] * ctx.canvas.width / ctx.canvas.clientWidth;
  pos[1] = pos[1] * ctx.canvas.height / ctx.canvas.clientHeight;
  return pos;
}

function getTransformRelativeMousePosition(e) {
  const pos = getCanvasRelativeMousePosition(e);
  const p = new DOMPoint(...pos);
  const point = inverseOriginTransform.transformPoint(p);
  return [point.x, point.y];
}

canvas.addEventListener('mousedown', (e) => {
  const pos = getTransformRelativeMousePosition(e);
  if (newStart) {
  } else {
    newStart = pos;
    newEnd = pos;
  }
});

canvas.addEventListener('mousemove', (e) => {
  if (newStart) {
    newEnd = getTransformRelativeMousePosition(e);
    draw();
  }
});

canvas.addEventListener('mouseup', (e) => {
  if (newStart) {
    lines.push([newStart, newEnd]);
    newStart = undefined;
  }
});


function draw() {
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  ctx.save();
  setZoomAndOffsetTransform();
  ctx.setTransform(
      originTransform.a,
      originTransform.b,
      originTransform.c,
      originTransform.d,
      originTransform.e,
      originTransform.f);
  ctx.beginPath();
  for (const line of lines) {
    ctx.moveTo(...line[0]);
    ctx.lineTo(...line[1]);
  }
  if (newStart) {
     ctx.moveTo(...newStart);
     ctx.lineTo(...newEnd);
  }
  ctx.stroke();
  ctx.restore();
}

function addSlider(obj, prop, parent, min, max, callback) {
  const valueRange = max - min;
  const sliderRange = 100;
  
  const div = document.createElement('div');
  div.class = 'range';
  
  const input = document.createElement('input');
  input.type = 'range';
  input.min = 0;
  input.max = sliderRange;
 
  const label = document.createElement('span');
  label.textContent = `${prop}: `;
  
  const valueElem = document.createElement('span');
  
  function setInputValue(v) {
    input.value = (v - min) * sliderRange / valueRange;
  }
  
  input.addEventListener('input', (e) => {
    const v = parseFloat(input.value) * valueRange / sliderRange + min;
    valueElem.textContent = v.toFixed(1);
    obj[prop] = v;
    callback();
  });
  
  const v = obj[prop];
  valueElem.textContent = v.toFixed(1);
  setInputValue(v);
  
  div.appendChild(input);
  div.appendChild(label);
  div.appendChild(valueElem);
  parent.appendChild(div);
}
canvas { border: 1px solid black; }
#app { display: flex; }
<div id="app"><canvas></canvas><div id="ui"></div>

注意:我没有费心将缩放始终从中心缩放。为此,需要在缩放更改时调整xoffset和yoffset。