Canvas缩放拖拽时无限固定网格实现及Offset计算问题求助
问题求助:Canvas缩放拖拽时无限固定网格的offset计算
我参考博客重构代码,使用Canvas变换替代手动偏移,却无法实现缩放拖拽时的无限固定网格。已多次修改drawGrid()函数,目前正在研读《HTML Canvas Deep Dive》和《The HTML5 Canvas Handbook》。将drawGrid()放在ctx.stop()与ctx.restore()之间后,拖拽时网格可循环,但基于鼠标点缩放时的offset计算存在问题,求助正确的计算方法。
相关代码
JavaScript
const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const wrapper = document.getElementsByClassName('wrapper')[0]; canvas.width = wrapper.clientWidth; canvas.height = wrapper.clientHeight; let scale = 1; let previousScale = 1; const maxScale = 3; const minScale = 0.5; const gridSize = 150; const gridColor = '#888'; let offsetX = 0; let offsetY = 0; let isDragging = false; let dragElement = null; let dragOffsetX = 0; let dragOffsetY = 0; let dragStartPosition = { x: 0, y: 0 }; let currentTransformedCursor = { x: 0, y: 0 }; class CanvasElement { constructor(x, y, shape) { this.x = x; this.y = y; this.shape = shape; } isMouseInside(ctx, mouseX, mouseY) { ctx.save(); ctx.beginPath(); this.drawPath(ctx); const isInside = ctx.isPointInPath(mouseX, mouseY); ctx.restore(); return isInside; } drawPath(ctx) { throw new Error('drawPath method must be implemented in the derived class'); } } class Rectangle extends CanvasElement { constructor(x, y, width, height, color) { super(x, y, 'rect'); this.width = width; this.height = height; this.color = color; } drawPath(ctx) { ctx.fillStyle = this.color; ctx.rect(this.x, this.y, this.width, this.height); } } class Circle extends CanvasElement { constructor(x, y, radius, color) { super(x, y, 'circle'); this.radius = radius; this.color = color; } drawPath(ctx) { ctx.fillStyle = this.color; ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI); } } class Triangle extends CanvasElement { constructor(x, y, size, color) { super(x, y, 'triangle'); this.size = size; this.color = color; } drawPath(ctx) { ctx.fillStyle = this.color; ctx.moveTo(this.x, this.y); ctx.lineTo(this.x + this.size, this.y); ctx.lineTo(this.x + this.size / 2, this.y + this.size); ctx.closePath(); } } function getTransformedPoint(x, y) { const originalPoint = new DOMPoint(x, y); return ctx.getTransform().invertSelf().transformPoint(originalPoint); } const handleMouseDown = (event) => { isDragging = true; dragStartPosition = getTransformedPoint(event.offsetX, event.offsetY); const mouseX = event.clientX - canvas.offsetLeft; const mouseY = event.clientY - canvas.offsetTop; dragElement = elements.find(element => element.isMouseInside(ctx, mouseX, mouseY)); if (dragElement) { dragOffsetX = mouseX - dragElement.x; dragOffsetY = mouseY - dragElement.y; } } const handleMouseMove = (event) => { currentTransformedCursor = getTransformedPoint(event.offsetX, event.offsetY); const mouseX = event.clientX - canvas.offsetLeft; const mouseY = event.clientY - canvas.offsetTop; const hoveredElement = elements.find(element => element.isMouseInside(ctx, mouseX, mouseY)); if (isDragging) { if (dragElement) { dragElement.x = mouseX - dragOffsetX; dragElement.y = mouseY - dragOffsetY; }else{ const deltaX = currentTransformedCursor.x - dragStartPosition.x; const deltaY = currentTransformedCursor.y - dragStartPosition.y; offsetX += deltaX; offsetY += deltaY; ctx.translate(deltaX, deltaY); } draw(); }else{ draw(); if (hoveredElement) { ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.beginPath(); hoveredElement.drawPath(ctx); ctx.stroke(); } } }; const handleMouseOut = (event) => { isDragging = false; dragElement = null; dragOffsetX = 0; dragOffsetY = 0; } const handleMouseUp = (event) => { isDragging = false; dragElement = null; dragOffsetX = 0; dragOffsetY = 0; } const handleWheel = (event) => { const zoomFactor = event.deltaY < 0 ? 1.1 : 0.9; scale = previousScale * zoomFactor; if (scale >= minScale && scale <= maxScale) { previousScale = scale; const deltaX = currentTransformedCursor.x - dragStartPosition.x; const deltaY = currentTransformedCursor.y - dragStartPosition.y; offsetX += deltaX; offsetY += deltaY; ctx.translate(currentTransformedCursor.x, currentTransformedCursor.y); ctx.scale(zoomFactor, zoomFactor); ctx.translate(-currentTransformedCursor.x, -currentTransformedCursor.y); draw(); event.preventDefault(); } } function stopTransform(canvas) { var context = canvas.getContext('2d'); context.setTransform(1, 0, 0, 1, 0, 0); } function drawGrid() { const updateGridSize = gridSize * scale; const gridOffsetX = offsetX % updateGridSize; const gridOffsetY = offsetY % updateGridSize; ctx.beginPath(); for (let x = gridOffsetX; x < canvas.width; x += updateGridSize) { ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); } for (let y = gridOffsetY; y < canvas.height; y += updateGridSize) { ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); } ctx.lineWidth = 1; ctx.strokeStyle = gridColor; ctx.stroke(); }; canvas.addEventListener('mousedown', handleMouseDown); canvas.addEventListener('mousemove', handleMouseMove); canvas.addEventListener('mouseout', handleMouseOut); canvas.addEventListener('mouseup', handleMouseUp); canvas.addEventListener('wheel', handleWheel); const rectangle = new Rectangle(50, 50, 100, 50, "#f00"); const circle = new Circle(200, 200, 50, "#0f0"); const circle2 = new Circle(360, 500, 90, "#af0"); const triangle = new Triangle(300, 100, 60, "#00f"); const elements = [rectangle, circle, circle2, triangle]; function draw() { ctx.save(); ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.clearRect(0, 0, canvas.width, canvas.height); drawGrid(); ctx.restore(); elements.forEach(element => { ctx.beginPath(); element.drawPath(ctx); ctx.fill(); }); } draw();
CSS
* { padding: 0; margin: 0; overflow: hidden; } html, body { width: 100%; height: 100%; background-color: #a1a1a1; } canvas { display: block; box-sizing: border-box; } .wrapper { display: flex; align-items: center; justify-content: center; height: 100vh; position: relative; z-index: 2; } .debug{ position: absolute; width:auto; height:auto; right:0; background-color:none; text-align: right; margin: 20px; z-index:9; }
HTML
<div class="wrapper"> <div> <canvas id="canvas"></canvas> </div> </div>
期望实现效果
拖拽时网格无缝循环延伸,缩放时以鼠标位置为中心,网格保持固定间距无限展开。
内容的提问来源于stack exchange,提问作者efex
相关产品推荐
相关产品推荐

