Canvas屏幕中心基准缩放问题求解(附代码)
Canvas屏幕中心缩放逻辑修正
核心原理
要实现以屏幕中心为基准的缩放,关键在于缩放过程中保持屏幕中心对应的世界坐标不变,具体步骤:
- 获取当前画布的缩放比例与偏移量
- 计算屏幕中心在世界坐标系中的对应位置
- 计算新的缩放比例
- 根据新缩放比例重新调整偏移量,确保屏幕中心的世界坐标位置不变
修正后的zoom函数
替换原代码中的zoom函数即可实现需求:
function zoom(p){ const currentTransform = ctx.getTransform(); const currentScale = currentTransform.a; const currentX = currentTransform.e; const currentY = currentTransform.f; // 屏幕中心的像素坐标 const screenCenterX = canvas.width / 2; const screenCenterY = canvas.height / 2; // 计算屏幕中心对应的世界坐标 const worldCenterX = (screenCenterX - currentX) / currentScale; const worldCenterY = (screenCenterY - currentY) / currentScale; let newScale; if (p === 1) { newScale = currentScale * 2; } else { // 限制最小缩放比例,避免缩放过小导致异常 newScale = Math.max(currentScale / 2, 0.125); } // 计算新偏移量,保证屏幕中心对应原世界坐标 const newX = screenCenterX - worldCenterX * newScale; const newY = screenCenterY - worldCenterY * newScale; ctx.setTransform(newScale, 0, 0, newScale, newX, newY); graphPaperDrawer(); }
完整代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>2D Circuit Simulator</title> <style> canvas { border: 1px solid white; background-color: black; } body { color: white; margin: 0px; background-color: black; } </style> </head> <body> <canvas id="canvas"></canvas> <button onclick="zoom(1)">Zoom+</button> <button onclick="zoom(-1)">Zoom-</button> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); canvas.width = 960; canvas.height = 600; var eleMatrix = []; var bodyTrans = new DOMMatrix([1, 0, 0, 1, 0, 0]); ctx.setTransform(bodyTrans); graphPaperDrawer(); function zoom(p){ const currentTransform = ctx.getTransform(); const currentScale = currentTransform.a; const currentX = currentTransform.e; const currentY = currentTransform.f; const screenCenterX = canvas.width / 2; const screenCenterY = canvas.height / 2; const worldCenterX = (screenCenterX - currentX) / currentScale; const worldCenterY = (screenCenterY - currentY) / currentScale; let newScale; if (p === 1) { newScale = currentScale * 2; } else { newScale = Math.max(currentScale / 2, 0.125); } const newX = screenCenterX - worldCenterX * newScale; const newY = screenCenterY - worldCenterY * newScale; ctx.setTransform(newScale, 0, 0, newScale, newX, newY); graphPaperDrawer(); } dragElement(canvas); function dragElement(elmnt) { var elpos = [], oldx = 0, oldy = 0, disx = 0, disy = 0, graphxy = [],cusdr, target; elmnt.onmousedown = dragMouseDown; elmnt.ontouchstart = dragMouseDown; function dragMouseDown() { event.preventDefault(); oldx = getXY(event).x; oldy = getXY(event).y; graphxy = [bodyTrans.e, bodyTrans.f,bodyTrans.a]; document.onmouseup = closeDragElement; document.ontouchend = closeDragElement; document.onmousemove = elementDrag; document.ontouchmove = elementDrag; } function elementDrag() { event.preventDefault(); disx = getXY(event).x - oldx; disy = getXY(event).y - oldy; var subtar = null; cusdr = [false]; ctx.setTransform(ctx.getTransform().a, 0, 0, ctx.getTransform().a, (graphxy[0] + disx), (graphxy[1] + disy)); graphPaperDrawer(); elpos = [ctx.getTransform().e,ctx.getTransform().f,ctx.getTransform().a]; } function closeDragElement() { bodyTrans = new DOMMatrix([elpos[2], 0, 0, elpos[2], elpos[0], elpos[1]]); document.onmouseup = null; document.ontouchend = null; document.onmousemove = null; document.ontouchmove = null; console.log(bodyTrans.e+","+bodyTrans.f) } } function graphPaperDrawer() { var ft = ctx.getTransform().a; ctx.clearRect(-ctx.getTransform().e/ft, -ctx.getTransform().f/ft, canvas.width/ft, canvas.height/ft); ctx.fillStyle= "red"; ctx.fillRect(0, 0, 100, 100); ctx.fillStyle = "green"; ctx.fillRect(860, 0, 100, 100); ctx.fillStyle = "blue"; ctx.fillRect(0, 500, 100, 100); ctx.fillStyle = "white"; ctx.fillRect(480, 300, 100, 100); } function getXY(event) { const deviceType = window.matchMedia("(pointer: coarse)").matches; var x, y; if (deviceType) { var x = event.touches[0].clientX; var y = event.touches[0].clientY; } else { var x = event.clientX; var y = event.clientY; } return { "x": x, "y": y }; } </script> </body> </html>
效果说明
修正后,点击缩放按钮时,画布会以屏幕中心(480,300像素位置)为基准放大/缩小,缩放过程中屏幕中心对应的世界元素位置保持不变,符合预期的中心缩放效果。
内容的提问来源于stack exchange,提问作者debarghya mondal
相关产品推荐
相关产品推荐

