[英]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 像素的方形画布。 使用我上面的标准化x
和y
,左上角是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.