You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Canvas缩放后绘制线条位置偏移的问题修复求助

Canvas缩放后绘制线条位置偏移的问题修复求助

我正在给Canvas做一个绘图功能,但卡在缩放这块了,有没有大佬能帮忙看看?

当处于完全缩小的状态时,我能精准地在鼠标光标位置绘制,但一旦放大,画出来的线条就和鼠标位置有偏移。

我现在用来计算drawLines函数的adjustedX和adjustedY的公式是:

const adjustedX = e.offsetX * viewportTransform.scale - viewportTransform.x;
const adjustedY = e.offsetY * viewportTransform.scale - viewportTransform.y;

能不能帮我修正这个问题?我把所有HTML和原生JS代码都附上了:

JavaScript 代码

function showLines() {
  console.log(lines)
}

function toggleDrawMode() {
  isDrawingMode = !isDrawingMode
}

const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
let lines = [];

const viewportTransform = {
  x: 0,
  y: 0,
  scale: 1
}

// From here on, everything we'll write will go below 
const drawRect = (x, y, width, height, color) => {
  ctx.fillStyle = color
  ctx.fillRect(x, y, width, height)
}


let isDrawingMode = true
const penWidth = 5
const penColor = 'red'

const drawPixels = (x, y) => {
  ctx.beginPath()

  ctx.arc(x, y, penWidth, 2 * Math.PI, false)
  ctx.fillStyle = penColor

  ctx.fill()
}

const erasePixels = (x, y) => {
  ctx.save()

  ctx.beginPath()
  ctx.arc(x, y, penWidth, 2 * Math.PI, false)

  ctx.globalCompositeOperation = 'destination-out'

  ctx.fillStyle = 'white'
  ctx.fill()
  ctx.restore()
}


const render = () => {

  // New code 
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.setTransform(viewportTransform.scale, 0, 0, viewportTransform.scale, viewportTransform.x, viewportTransform.y);
  // New Code 

  // drawRect(0, 0, 100, 100, 'red');
  // drawRect(200, 200, 100, 100, 'blue');
  // drawPixels(400, 400)

  // Ok, so we DO need to redraw the lines. That means we DO need to KEEP TRACK of them!
  // This drawing just needs absolute figures, the same as they were when the line was being made! The viewportTransform will automatically adjust shit here and there.
  lines.forEach((line, idx) => {

    // console.log(`letsdraw this line #${idx}`, line, `from the start pos (${previousXDrawing}, ${previousYDrawing})`);

    // drawPixels(line.x, line.y)
    if (line.isEraseLine) {
      eraseLine(line.x, line.y, line.previousX, line.previousY)
    } else {
      drawLine(line.x, line.y, line.previousX, line.previousY)
    }


    // TODO: bullshit most likely
    // previousXDrawing = line.x
    // previousYDrawing = line.y
  });

}


render()
// drawPixels(400, 400)
// lines.push({ x: 400, y: 400 })


// We need to keep track of our previous mouse position for later
let previousX = 0,
  previousY = 0;
let previousXDrawing = 0,
  previousYDrawing = 0;

const updatePanning = (e) => {
  const localX = e.clientX;
  const localY = e.clientY;

  viewportTransform.x += localX - previousX;
  viewportTransform.y += localY - previousY;

  previousX = localX;
  previousY = localY;
}

// If we are trying to zoom out to a level lesser than zoom level 1, then we will not do anything
const isZoomAllowed = (viewportTransform, deltaY) => {
  return viewportTransform.scale + deltaY * -0.01 >= 1
}

const updateZooming = (e) => {
  const oldScale = viewportTransform.scale;
  const oldX = viewportTransform.x;
  const oldY = viewportTransform.y;

  const localX = e.clientX;
  const localY = e.clientY;

  const previousScale = viewportTransform.scale;

  const newScale = viewportTransform.scale += e.deltaY * -0.01;

  const newX = localX - (localX - oldX) * (newScale / previousScale);
  const newY = localY - (localY - oldY) * (newScale / previousScale);

  viewportTransform.x = newX;
  viewportTransform.y = newY;
  viewportTransform.scale = newScale;
}


const drawLine = (x, y, previousPosX, previousPosY) => {
  ctx.beginPath()
  ctx.moveTo(previousPosX, previousPosY)
  ctx.lineTo(x, y)

  ctx.strokeStyle = penColor
  ctx.lineWidth = penWidth
  ctx.lineCap = 'round'

  ctx.stroke()
}

const eraseLine = (x, y, previousPosX, previousPosY) => {
  ctx.save()

  ctx.beginPath()
  ctx.moveTo(previousPosX, previousPosY)
  ctx.lineTo(x, y)

  ctx.globalCompositeOperation = 'destination-out'

  // ctx.strokeStyle = 'white' // NOT REALLY NEEDED. Destination out means you're just removing the effects of the pen, regardless of whatever colors they were.
  ctx.lineWidth = penWidth
  ctx.lineCap = 'round'

  ctx.stroke()
  ctx.restore()
}


const onMouseMove = (e) => {

  // Handle the case of drawing here. If drawing mode is on, then that should be handled.

  if (isDrawingMode) {



    // FINAL: yes this is the final solution! I have got it! Eureka!!!! yippieee
    // TODO: change this PART B
    const adjustedX = e.offsetX * viewportTransform.scale - viewportTransform.x;
    const adjustedY = e.offsetY * viewportTransform.scale - viewportTransform.y;

    // const adjustedX = (e.clientX + viewportTransform.x) * viewportTransform.scale ;
    // const adjustedY = (e.clientY + viewportTransform.y) * viewportTransform.scale ;

    console.log('X walay: ', e.offsetX, viewportTransform.scale, viewportTransform.x);
    console.log('Y walay: ', e.offsetY, viewportTransform.scale, viewportTransform.y);

    var bounding = canvas.getBoundingClientRect();
    var x = e.clientX - bounding.left;
    var y = e.clientY - bounding.top;




    const xToDraw = adjustedX
    const yToDraw = adjustedY

    if (e.shiftKey) {
      // erasePixels(xToDraw, yToDraw)
      eraseLine(xToDraw, yToDraw, previousXDrawing, previousYDrawing)
      lines.push({
        x: xToDraw,
        y: yToDraw,
        previousX: previousXDrawing,
        previousY: previousYDrawing,
        isEraseLine: true
      }) // TODO: of course we somehow need to remember that this is a erased line. and handle it that way
    } else {

      // drawPixels(xToDraw, yToDraw)

      // drawPixels(400, 400)
      drawLine(xToDraw, yToDraw, previousXDrawing, previousYDrawing)
      lines.push({
        x: xToDraw,
        y: yToDraw,
        previousX: previousXDrawing,
        previousY: previousYDrawing,
        isEraseLine: false
      })


    }
    // bogus attempt TODO: problem either here!
    previousXDrawing = adjustedX
    previousYDrawing = adjustedY

    console.log('on moving of mouse, these were the assigned prev x and y: ', previousXDrawing, previousYDrawing);



  } else {
    updatePanning(e)
    render()
  }

  // updatePanning(e)
  // render()

  // console.log(e)
}

const onMouseWheel = (e) => {

  if (isZoomAllowed(viewportTransform, e.deltaY)) {
    updateZooming(e)
    render()
  }

  // console.log(e)
}

canvas.addEventListener("wheel", onMouseWheel);


canvas.addEventListener("mousedown", (e) => {
  // This is needed for ensuring smooth panning
  previousX = e.clientX;
  previousY = e.clientY;

  // This is my attempt to make the drawLine logic work TODO: or problem is here
  if (isDrawingMode) {

    // TODO: change this PARTA
    const adjustedX = e.offsetX * viewportTransform.scale - viewportTransform.x;
    const adjustedY = e.offsetY * viewportTransform.scale - viewportTransform.y;

    // const adjustedX = (e.clientX + viewportTransform.x) * viewportTransform.scale;
    // const adjustedY = (e.clientY + viewportTransform.y) * viewportTransform.scale ;

    var bounding = canvas.getBoundingClientRect();
    var x = e.clientX - bounding.left;
    var y = e.clientY - bounding.top;

    previousXDrawing = adjustedX
    previousYDrawing = adjustedY

    // previousXDrawing = e.offsetX
    // previousYDrawing = e.offsetY

    // TODO: for some reason, over here e.offsetX is a lot. 
    // maybe we are using the wrong key for comparing x and y coordinates for drawing, and need something else
    console.log('in adding event listener, these were prev X and Y: ', previousXDrawing, previousYDrawing)
  }


  canvas.addEventListener("mousemove", onMouseMove);
})

canvas.addEventListener("mouseup", (e) => {
  canvas.removeEventListener("mousemove", onMouseMove);
})

HTML 代码

<canvas width="500" height="500" id="canvas" style="border: 2px solid black; background-color: yellow;"></canvas>

<label for="">Drawing Mode</label>
<input type="checkbox" checked onchange="toggleDrawMode()">

<button onclick="showLines()">Show stats of lines</button>

我还录了问题的演示视频,能直观看到缩放后绘制偏移的具体情况。


备注:内容来源于stack exchange,提问作者Abdullah Ahmad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 16:19:33