簡體   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