【问题标题】:Canvas doesn't follow actual position of mouse画布不跟随鼠标的实际位置
【发布时间】:2020-09-20 16:47:19
【问题描述】:

我跟随Draw on HTML5 Canvas using a mouse在convas中绘制免费共享,提供代码sn-p不能正常工作,但是当我尝试使用时,它不能正常工作。我的意思是光标的位置与画布中的不同。

截图

这里是代码 sn-ps


function init() {
  canvas = document.getElementById("whiteboard-canvas");
  ctx = canvas.getContext("2d");
  w = canvas.width;
  h = canvas.height;

  canvas.addEventListener(
    "mousemove",
    function (e) {
      findxy("move", e);
      console.log("on mouse move", e);
    },
    false
  );
#othere mouse event handler goes here..
}

function draw() {
  ctx.beginPath();
  ctx.moveTo(prevX, prevY);
  ctx.lineTo(currX, currY);
  ctx.strokeStyle = x;
  ctx.lineWidth = y;
  ctx.stroke();
  ctx.closePath();
}

function findxy(res, e) {
  if (res == "down") {
    prevX = currX;
    prevY = currY;
    currX = e.clientX - canvas.offsetLeft;;
    currY = e.clientY - canvas.offsetTop;;

    flag = true;
    dot_flag = true;
    if (dot_flag) {
      ctx.beginPath();
      ctx.fillStyle = x;
      ctx.fillRect(currX, currY, 2, 2);
      ctx.closePath();
      dot_flag = false;
    }
  }
  if (res == "up" || res == "out") {
    flag = false;
  }
  if (res == "move") {
    if (flag) {
      prevX = currX;
      prevY = currY;
      currX = e.clientX - canvas.offsetLeft;;
      currY = e.clientY - canvas.offsetTop;;
      draw();
    }
  }
}

我的代码 sn-ps 与链接中的代码几乎相同。

【问题讨论】:

  • 首先,JS cmets 不以# 开头 - 这是无效的,当您在浏览器中启动控制台时,您应该立即注意到这一点。其次,从你的 sn-p 你我看不出任何本质上的错误,除了 ctx.fillStyle = x; 没有意义,x 通常是一个坐标,所以用它作为填充没有意义,但因为我不知道x 来自哪里,我不能说这是否会导致你的问题。简而言之,将您的变量定义也添加到此帖子中(包括canvasctx 等...),并将其转换为 SO sn-p(其 <> 文档图标)。
  • 您必须缩放鼠标坐标(以 CSS 像素为单位)以匹配画布分辨率(画布像素)。该示例不包含执行此操作的代码,并且有许多警告。最基本的是canvasPixelX = currX * (canvas.width / canvas.getBoundingClientRect().width * devicePixelRatio) 和高度相同。请参阅developer.mozilla.org/en-US/docs/Web/API/Element/… 正确使用`getBoundingClientRect`和developer.mozilla.org/en-US/docs/Web/API/Window/… for devicePixelRatio
  • @Blindman67 这不是真的,offsetX 提供了正确的值,不需要缩放。
  • @somethinghere offsetX 和 Y 给出了左上角,它不提供与 CSS 像素相比画布像素大小的任何信息。画布分辨率与画布显示大小无关。在默认状态下,画布坐标是画布像素而不是 CSS 像素。如果 OP 有视网膜(或许多高清设备之一)显示 devicePixelRatio 也必须用于正确缩放
  • 你在树林里下车,伙计。问题不在于。没有迹象表明这与 DPI 有任何关系。你让问题变得不必要地复杂了。由于没有视网膜指标,所有这些都匹配。即便如此,您也可以使用简单的 context.scale() 来解决这个问题,然后完全忘记它。这家伙刚刚起步,视网膜没有问题。

标签: canvas html5-canvas


【解决方案1】:

CSS 到画布像素坐标

画布分辨率(以像素为单位)和画布页面大小(以CSS pixels 为单位)是相互独立的。

使用 2D API 时,渲染是在画布像素(不是 CSS 像素)中完成的

鼠标事件将坐标保存为CSS pixels,因此不会始终直接转换为画布像素坐标。

可以使用getBoundingClientRect 获取画布的页面大小,它可用于从 CSS 像素缩放到画布像素。

但是getBoundingClientRect 包括元素的内边距和边框,在将 CSS 像素缩放到画布像素时也必须考虑这一点。

注意 在我的 cmets 中,我谈到了设备像素比。我对它的用途的评估是错误的,在这种情况下不需要它

示例

该示例使用默认画布(300 x 150 像素),然后使用 CSS 规则将其缩放到页面,以 CSS 像素以外的单位添加填充和边框。画布也被缩放,可以上下滚动。

在整页和 sn-p 之间切换以查看缩放对示例的影响。

函数getInnerRect 使用getBoundingClientRectgetComputedStyle 以CSS 像素获取页面上画布的边界。从画布左上角像素的左上角到最右下角像素的右下角。

draw 函数然后使用内部矩形(在示例中命名为bounds)来计算比例(x,y)。然后偏移鼠标坐标并缩放它们以绘制到画布上。

  • 注意函数mouseEventevent.pageXevent.pageY捕获鼠标坐标

  • 注意函数getBoundingClientRect是相对于页面滚动位置的。因此,当使用它的返回时,必须调整坐标以计算页面滚动位置。该示例使用scrollXscrollY 来执行此操作。

  • 注意,缩放和偏移也可以封装为 2D 变换。由于示例中的坐标仅在调整页面大小时才会改变,因此可以在调整大小时计算一次变换并将其应用于画布。因此,draw 函数可以使用 CSS 像素直接渲染到画布上。这使得示例中的代码更简单一些。 (见第二个sn-p);

  • 注意 因为变换会变换所有渲染尺寸,所以有必要计算变换的反比例。这用于在第二个示例中缩放线宽。如果不这样做,则渲染的线宽将随着页面大小的变化而变化。

const ctx = canvas.getContext("2d");
var bounds;
const mouse = {x:0,y:0,b:0,px:0,py:0}; // p for previouse b for button
function mouseEvent(event) {
    mouse.px = mouse.x;
    mouse.py = mouse.y;
    mouse.x = event.pageX;
    mouse.y = event.pageY;
    if (event.type === "mousedown") {
        mouse.b = true;
        canvas.classList.add("drawing");
    } else if (event.type === "mouseup" || event.type === "mouseout") { 
        mouse.b = false;
        canvas.classList.remove("drawing");
    }
    draw();
}
addEventListener("mousemove", mouseEvent);
addEventListener("mousedown", mouseEvent);
addEventListener("mouseup", mouseEvent);
addEventListener("mouseout", mouseEvent);

const CSSPx2Number = cssPx => Number(cssPx.replace("px",""));
function getInnerRect(element) {
    var top, left, right, bottom;
    const bounds = element.getBoundingClientRect();
    const canStyle = getComputedStyle(element);
    
    left  = CSSPx2Number(canStyle.paddingLeft);
    left += CSSPx2Number(canStyle.borderLeftWidth);
    
    top  = CSSPx2Number(canStyle.paddingTop);
    top += CSSPx2Number(canStyle.borderTopWidth);
    
    right  = CSSPx2Number(canStyle.paddingRight);
    right += CSSPx2Number(canStyle.borderRightWidth);
    
    bot  = CSSPx2Number(canStyle.paddingBottom);
    bot += CSSPx2Number(canStyle.borderBottomWidth);       
    
    return {
        top: bounds.top + top  + scrollY,
        left: bounds.left + left  + scrollX,
        bottom: bounds.bottom + bot + scrollY,
        right: bounds.right + right + scrollX,
        width: bounds.width - left - right,
        height: bounds.height - top - bot,
    };


}
function resizeEvent() { 
    bounds = getInnerRect(canvas);
    widthText.textContent = "Canvas page size: " + bounds.width.toFixed(1) + " by " + bounds.height.toFixed(1) + "px Canvas width: " + canvas.width + " by " + canvas.height + "px";
    ctx.lineWidth = 3;
    ctx.strokeStyle = "black";
    ctx.lineCap = ctx.lineJoin = "round";
}
addEventListener("resize",resizeEvent);
resizeEvent();

function draw() {
    if (mouse.b) {
        const xScale = canvas.width / bounds.width;
        const yScale = canvas.height / bounds.height;
        
        const x = (mouse.x - bounds.left) * xScale;
        const y = (mouse.y - bounds.top) * yScale;
        const px = (mouse.px - bounds.left) * xScale;
        const py = (mouse.py - bounds.top) * yScale;

        ctx.beginPath();
        ctx.lineTo(px, py);
        ctx.lineTo(x, y);
        ctx.stroke();
    }
}
canvas {
    position: absolute;
    top: 5%;
    left: 10%;
    width: 80%;
    height: 170%;
    border: 1pc solid blue;
    padding: 1%;
    cursor: crosshair;
}
.drawing { cursor: none; }
.info { 
   position: absolute;
   font-family: arial;
   font-size: x-small;
   pointer-events: none; 
   text-align: center;
   background: white;
   border: 1px solid black;

}
#widthText {
  top: 2%;
  left: 20%;
  width: 60%;
}
<!-- default size of canvas is 300 by 150 -->
<canvas id="canvas"></canvas>
<div id="widthText" class="info"></div>

使用变换

const ctx = canvas.getContext("2d");
const matrix = [1,0,0,1,0,0];
const mouse = {x:0,y:0,b:0,px:0,py:0}; // p for previouse b for button
function mouseEvent(event) {
    mouse.px = mouse.x;
    mouse.py = mouse.y;
    mouse.x = event.pageX;
    mouse.y = event.pageY;
    if (event.type === "mousedown") {
        mouse.b = true;
        canvas.classList.add("drawing");
    } else if (event.type === "mouseup" || event.type === "mouseout") { 
        mouse.b = false;
        canvas.classList.remove("drawing");
    }
    draw();
}
addEventListener("mousemove", mouseEvent);
addEventListener("mousedown", mouseEvent);
addEventListener("mouseup", mouseEvent);
addEventListener("mouseout", mouseEvent);



function draw() {
    if (mouse.b) {
        ctx.beginPath();  // using CSS pixels
        ctx.lineTo(mouse.px, mouse.py);
        ctx.lineTo(mouse.x, mouse.y);
        ctx.stroke();
    }
}


const CSSPx2Number = cssPx => Number(cssPx.replace("px",""));
function getInnerRect(element) {

    var top, left, right, bottom;
    const bounds = element.getBoundingClientRect();
    const canStyle = getComputedStyle(element);
    
    left  = CSSPx2Number(canStyle.paddingLeft);
    left += CSSPx2Number(canStyle.borderLeftWidth);
    
    top  = CSSPx2Number(canStyle.paddingTop);
    top += CSSPx2Number(canStyle.borderTopWidth);
    
    right  = CSSPx2Number(canStyle.paddingRight);
    right += CSSPx2Number(canStyle.borderRightWidth);
    
    bot  = CSSPx2Number(canStyle.paddingBottom);
    bot += CSSPx2Number(canStyle.borderBottomWidth);      
    
    const canvasInner = {
        top: bounds.top + top + scrollY,
        left: bounds.left + left  + scrollX,
        bottom: bounds.bottom + bot + scrollY,
        right: bounds.right + right + scrollX,
        width: bounds.width - left - right,
        height: bounds.height - top - bot,

    };
    const xScale = canvas.width / canvasInner.width;
    const yScale = canvas.height / canvasInner.height;

    // use mean of x,y scale to get inverse scale
    canvasInner.invScale = 1 / ((xScale + yScale) / 2);

    // set scale 
    matrix[0] = xScale;
    matrix[3] = yScale;
    
    // set offset in canvas pixels
    matrix[4] = -canvasInner.left * xScale;
    matrix[5] = -canvasInner.top * yScale;    

    ctx.setTransform(...matrix);
    return canvasInner;

}
function resizeEvent() { 
    const bounds = getInnerRect(canvas);
    widthText.textContent = "Canvas page size: " + bounds.width.toFixed(1) + " by " + bounds.height.toFixed(1) + "px Canvas width: " + canvas.width + " by " + canvas.height + "px";
    ctx.lineWidth = 3 * bounds.invScale;  // correcting line width for render
    ctx.strokeStyle = "black";
    ctx.lineCap = ctx.lineJoin = "round";  
}
addEventListener("resize",resizeEvent);
resizeEvent();
canvas {
    position: absolute;
    top: 5%;
    left: 10%;
    width: 80%;
    height: 170%;
    border: 1pc solid green;
    padding: 1%;
    cursor: crosshair;
}
.drawing { cursor: none; }
.info { 
   position: absolute;
   font-family: arial;
   font-size: x-small;
   pointer-events: none; 
   text-align: center;
   background: white;
   border: 1px solid black;
}
#widthText {
  top: 2%;
  left: 20%;
  width: 60%;
}
<!-- default size of canvas is 300 by 150 -->
<canvas id="canvas"></canvas>
<div id="widthText" class="info"></div>

【讨论】:

  • 很棒的解释,谢谢
猜你喜欢
  • 1970-01-01
  • 2013-12-30
  • 2013-06-12
  • 2019-04-25
相关资源
最近更新 更多