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
相关产品推荐
相关产品推荐

