繁体   English   中英

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

[英]Getting relative mouse position on canvas after scaling

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

原因:为了计算一行的开始和结束位置,我将捕获时的输入标准化为 0 和 1 之间(含),如下所示:

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

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

要实现一个放大的滚动功能,我只是translate基于当前鼠标的位置, scale由2倍的帆布,然后translate回当前鼠标位置的负值(如建议在这里)。

这就是问题所在

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

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

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

结果是,当我放大并绘制时,线条出现在它们本来的位置,就好像我根本没有放大一样。 如果我放大中心并在左上角“绘制”,我将什么也看不到,直到我滚动出来看到这条线实际上是在原来的左上角绘制的。

我需要知道的是:缩放后,有没有办法了解您的新原点相对于未缩放画布的位置,或者隐藏了多少画布? 这些中的任何一个都可以让我放大并绘制并正确跟踪。


如果我在这里完全偏离基地并且有更好的方法来解决这个问题,我全神贯注。 如果您需要其他信息,我会提供我能提供的信息。

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

放大 =

  • 使画布大小不同?

  • 改变了画布上的变换

  • 使用 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。

使用 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();
});

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM