微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

将鼠标位置转换为画布坐标并返回

如何解决将鼠标位置转换为画布坐标并返回

我正在创建一个带有叠加 div 的画布,以在单击时添加标记,我希望标记在平移缩放画布或调整窗口大小时改变位置。我正在使用 https://github.com/timmywil/panzoom 进行平移缩放。

问题是,当我将鼠标位置转换为画布坐标时,它可以正常工作,但是当我将其转换回屏幕位置以在叠加 div 上呈现标记时,结果与初始化的鼠标位置不同,并在调整大小时重新计算标记的位置不正确。

这个画布是全屏的,没有滚动。

width = 823; height = 411;
scale = 2; panX = 60; panY = 10;
mouse.pageX = 467; mouse.pageY = 144; 

// {x: 475,y: 184} correct coords when I use ctx.drawImage(..) to test
canvasCoords = getCanvasCoords(mouse.pageX,mouse.pageY,scale);

// {x: 417,y: 124}
screenCoords = toScreenCoords(canvasCoords.x,canvasCoords.y,scale,panX,panY);
------------------------------
but with scale = 1; it worked correctly.
// convert mouse position to canvas coordinates
getCanvasCoords(pageX: number,pageY: number,scale: number) {
    var rect = this.pdfInfo.canvas.getBoundingClientRect();
    let x = (pageX - rect.left + this.scrollElement.scrollTop) / scale;
    let y = (pageY - rect.top + this.scrollElement.scrollLeft) / scale;
    return {
      x: Number.parseInt(x.toFixed(0)),y: Number.parseInt(y.toFixed(0)),};
}

// convert canvas coords to screen coords 
toScreenCoords(
    x: number,y: number,scale: number
  ) {
    var rect = this.pdfInfo.canvas.getBoundingClientRect();

    let wx =
      x * scale + rect.left - this.scrollElement.scrollTop / scale;
    let wy =
      y * scale + rect.top - this.scrollElement.scrollLeft / scale;
    return {
      x: Number.parseInt(wx.toFixed(0)),y: Number.parseInt(wy.toFixed(0)),};
}

getNewPos(x,oldV,newV) {
    return (x * oldV) / newV;
}

// update screen coords with new screen width and height
onResize(old,new) {
   this.screenCoordList.forEach(el => {
      el.x = getNewPos(el.x,old.width,new.width);
      el.y = getNewPos(el.y,old.height,new.height);
   })
}

如何让它与规模和平移一起工作?如果您知道任何图书馆可以完成这项工作,请推荐,谢谢。

解决方法

这是一个似乎有效的代码片段,您可以根据自己的目的对其进行调整。

我使用的是:

function toCanvasCoords(pageX,pageY,scale) {
    var rect = canvas.getBoundingClientRect();
    let x = (pageX - rect.left) / scale;
    let y = (pageY - rect.top) / scale;
    return toPoint(x,y);
}

function toScreenCoords(x,y,scale) {
    var rect = canvas.getBoundingClientRect();
    let wx = x * scale + rect.left + scrollElement.scrollLeft;
    let wy = y * scale + rect.top + scrollElement.scrollTop;
    return toPoint(wx,wy);
}

我只是从 window 对象获取鼠标位置。我可能弄错了,但我认为这就是 scrollLeftscrollTop 没有出现在 toCanvasCoords 中的原因(因为位置是相对于窗口本身的客户区,滚动没有进入它)。但是当你变回时,你必须考虑到它。

这最终只是返回相对于窗口(即输入)的鼠标位置,因此如果您只想将元素附加到鼠标指针,则没有必要以迂回的方式进行整个转换。但是,如果您想将某些东西附加到画布图像上的某个点(例如,地图上的某个特征),则转换回很有用 - 我猜这就是您想要的东西,因为您说过想要在叠加层 div 上呈现标记。

在下面的代码片段中,红色圆圈是在画布本身的 toCanvasCoords 返回的位置绘制的;您会注意到它与背景一起缩放。

我没有使用覆盖整个地图的覆盖 div,我只是使用绝对定位在顶部放置了几个小 div。黑色三角形是一个div(#tracker),主要是跟踪鼠标;它放在 toScreenCoords 的结果中。它可以作为一种检查转换是否正常工作的方法。它是一个独立的元素,因此不会随图像缩放。

红色三角形是另一个这样的 div (#feature),它展示了前面提到的“附加到特征”的想法。假设背景是一个类似于地图的东西,并且假设您想将“地图图钉”图标附加到其上的某物上,例如特定的交叉点;您可以在地图上获取该位置(这是一个固定值),并将其传递给 toScreenCoords。在下面的代码片段中,我将它与背景上的一个正方形的角对齐,以便您可以在更改比例和/或滚动时直观地跟踪它。 (点击“运行代码片段”后,您可以点击“整页”,然后调整窗口大小以获得滚动条)。

现在,根据您的代码究竟发生了什么,您可能已经调整了一些东西,但希望这会对您有所帮助。如果遇到问题,请使用 console.log 和/或在页面上放置一些调试元素,这些元素将为您实时显示值(例如鼠标位置、客户端矩形等),以便您可以检查值。并且一步到位——例如首先让比例工作,但忽略滚动,然后尝试让滚动工作,但保持比例为 1,依此类推。

const canvas = document.getElementById('canvas');
const context = canvas.getContext("2d");
const tracker = document.getElementById('tracker');
const feature = document.getElementById('feature');
const slider = document.getElementById("scale-slider");
const scaleDisplay = document.getElementById("scale-display");
const scrollElement = document.querySelector('html');

const bgImage = new Image();
bgImage.src = "https://i.stack.imgur.com/yxtqw.jpg"
var bgImageLoaded = false;
bgImage.onload = () => { bgImageLoaded = true; };

var mousePosition = toPoint(0,0);
var scale = 1;

function updateMousePosition(evt) {
  mousePosition = toPoint(evt.clientX,evt.clientY);
}

function getScale(evt) {
  scale = evt.target.value;
  scaleDisplay.textContent = scale;
}

function toCanvasCoords(pageX,y);
}

function toScreenCoords(x,wy);
}

function toPoint(x,y) {
  return { x: x,y: y }
}

function roundPoint(point) {
  return {
    x: Math.round(point.x),y: Math.round(point.y)
  }
}

function update() {
  context.clearRect(0,500,500);
  context.save();
  context.scale(scale,scale);
  
  if (bgImageLoaded) 
    context.drawImage(bgImage,0);
  
  const canvasCoords = toCanvasCoords(mousePosition.x,mousePosition.y,scale);
  drawTarget(canvasCoords);
  
  const trackerCoords = toScreenCoords(canvasCoords.x,canvasCoords.y,scale);
  updateTrackerLocation(trackerCoords);

  updateFeatureLocation()
  
  context.restore();
  requestAnimationFrame(update);
}

function drawTarget(location) {
  context.fillStyle = "rgba(255,128,0.8)";
  context.beginPath();
  context.arc(location.x,location.y,8.5,2*Math.PI); 
  context.fill();
}

function updateTrackerLocation(location) {
  const canvasRectangle = offsetRectangle(canvas.getBoundingClientRect(),scrollElement.scrollLeft,scrollElement.scrollTop);
  if (rectContains(canvasRectangle,location)) {
    tracker.style.left = location.x + 'px';
    tracker.style.top = location.y + 'px';
  }
}

function updateFeatureLocation() {
  // suppose the background is a map,and suppose there's a feature of interest
  // (e.g. a road intersection) that you want to place the #feature div over
  // (I roughly aligned it with a corner of a square).
  const featureLoc = toScreenCoords(84,85,scale);
  feature.style.left = featureLoc.x + 'px';
  feature.style.top = featureLoc.y + 'px';
}

function offsetRectangle(rect,offsetX,offsetY) {
  // copying an object via the spread syntax or 
  // using Object.assign() doesn't work for some reason
  const result = JSON.parse(JSON.stringify(rect));
  result.left += offsetX;
  result.right += offsetX;
  result.top += offsetY;
  result.bottom += offsetY;
  result.x = result.left;
  result.y = result.top;
  
  return result;
}

function rectContains(rect,point) {
  const inHorizontalRange = rect.left <= point.x && point.x <= rect.right;
  const inVerticalRange = rect.top <= point.y && point.y <= rect.bottom;
  return inHorizontalRange && inVerticalRange;
}

window.addEventListener('mousemove',(e) => updateMousePosition(e),false);
slider.addEventListener('input',(e) => getScale(e),false);
requestAnimationFrame(update);
#canvas {
  border: 1px solid gray;
}

#tracker,#feature {
  position: absolute;
  left: 0;
  top: 0;  
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;  
  border-bottom: 10px solid black;
  transform: translate(-4px,0);
}

#feature {
  border-bottom: 10px solid red;
}
<div>
  <label for="scale-slider">Scale:</label>
  <input type="range" id="scale-slider" name="scale-slider" min="0.5" max="2" step="0.02" value="1">
  <span id="scale-display">1</span>
</div>
<canvas id="canvas" width="500" height="500"></canvas>
<div id="tracker"></div>
<div id="feature"></div>

附言不要做Number.parseInt(x.toFixed(0));通常,尽可能长时间使用浮点数以最大程度地减少错误累积,并且只在最后一分钟转换为 int。我已经包含了 roundPoint 函数,该函数将一个点的 (x,y) 坐标舍入到最近的整数(通过 Math.round),但最终根本不需要使用它。

注意:下图在代码片段中用作背景,作为缩放的参考点;它包含在这里只是为了将它托管在 Stack Exchange 的 imgur.com 帐户上,这样代码就不会引用(可能不稳定的)第三方源。
enter image description here

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。