HTML Canvas拖拽圆形添加吸附功能需求与代码求助
Canvas可拖拽圆形的吸附功能实现
针对你需要的两个核心功能,下面是完善后的代码实现,同时附上关键逻辑的说明:
完整代码
HTML部分(无需修改)
<!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>Canvas Snap Drag Circle</title> </head> <body> <canvas id="myCanvas" style="border: 1px solid black"></canvas> <script src="./script.js"></script> </body> </html>
JavaScript部分
var c = document.getElementById('myCanvas'); var ctx = c.getContext('2d'); width = c.width = window.innerWidth * 0.9; height = c.height = window.innerHeight * 0.9; // 定义吸附相关常量 const SNAP_RADIUS = 50; // 触发吸附的范围 const SNAP_DRAG_THRESHOLD = 40; // 吸附后需要拖动的距离阈值 // 计算两条线的交点坐标 const intersectionPoint = { x: width / 6, y: height / 2 }; var handle = { x: width / 2, y: height / 2, radius: 30, }; // 状态变量 let isSnapped = false; let dragStartOffset = { x: 0, y: 0 }; function draw() { ctx.clearRect(0, 0, width, height); ctx.beginPath(); ctx.arc(handle.x, handle.y, handle.radius, 0, Math.PI * 2, false); ctx.fill(); ctx.stroke(); drawLines(); // 绘制吸附范围提示(可选,方便调试) ctx.beginPath(); ctx.arc(intersectionPoint.x, intersectionPoint.y, SNAP_RADIUS, 0, Math.PI * 2, false); ctx.strokeStyle = '#ccc'; ctx.setLineDash([5, 5]); ctx.stroke(); ctx.setLineDash([]); } function drawLines() { ctx.beginPath(); ctx.moveTo(0, height / 2); ctx.lineTo(width, height / 2); ctx.stroke(); ctx.beginPath(); ctx.moveTo(width / 6, 0); ctx.lineTo(width / 6, height); ctx.stroke(); } function circlePointCollision(x, y, circle) { return distanceXY(x, y, circle.x, circle.y) < circle.radius; } function distanceXY(x0, y0, x1, y1) { var dx = x1 - x0, dy = y1 - y0; return Math.sqrt(dx * dx + dy * dy); } document.addEventListener('mousedown', function (e) { // 转换鼠标坐标为Canvas内部坐标(解决Canvas偏移问题) const rect = c.getBoundingClientRect(); const mouseX = e.clientX - rect.left; const mouseY = e.clientY - rect.top; if (isSnapped) { // 记录拖拽开始时鼠标与圆心的偏移 dragStartOffset.x = mouseX - handle.x; dragStartOffset.y = mouseY - handle.y; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); } else if (circlePointCollision(mouseX, mouseY, handle)) { document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); } }); function onMouseMove(e) { const rect = c.getBoundingClientRect(); const mouseX = e.clientX - rect.left; const mouseY = e.clientY - rect.top; if (isSnapped) { // 计算当前鼠标与拖拽起始点的距离 const dragDistance = distanceXY( mouseX - dragStartOffset.x, mouseY - dragStartOffset.y, handle.x, handle.y ); // 超过阈值则取消吸附,开始跟随鼠标移动 if (dragDistance > SNAP_DRAG_THRESHOLD) { isSnapped = false; handle.x = mouseX; handle.y = mouseY; } } else { handle.x = mouseX; handle.y = mouseY; // 判断是否进入吸附范围 const distToIntersection = distanceXY(handle.x, handle.y, intersectionPoint.x, intersectionPoint.y); if (distToIntersection < SNAP_RADIUS) { // 吸附到交点 handle.x = intersectionPoint.x; handle.y = intersectionPoint.y; isSnapped = true; } } draw(); } function onMouseUp() { // 如果未处于吸附状态,检查是否需要吸附到交点 if (!isSnapped) { const distToIntersection = distanceXY(handle.x, handle.y, intersectionPoint.x, intersectionPoint.y); if (distToIntersection < SNAP_RADIUS) { handle.x = intersectionPoint.x; handle.y = intersectionPoint.y; isSnapped = true; } } document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } draw();
关键逻辑说明
吸附范围与拖拽阈值
SNAP_RADIUS:定义圆形进入多大范围时触发吸附,可根据需求调整SNAP_DRAG_THRESHOLD:吸附后需要拖动的最小距离,超过这个距离才会取消吸附,实现"更大力度拖动"的效果
交点坐标计算
- 直接根据两条线的参数计算交点:水平中线
y=height/2和垂直线x=width/6的交点就是(width/6, height/2)
- 直接根据两条线的参数计算交点:水平中线
鼠标坐标转换
- 使用
canvas.getBoundingClientRect()将鼠标的客户端坐标转换为Canvas内部的相对坐标,避免Canvas在页面中有偏移时出现坐标错误
- 使用
吸附状态管理
isSnapped:标记圆形是否处于吸附状态- 当圆形进入吸附范围时,自动移动到交点并标记为吸附状态
- 吸附状态下点击圆形,记录鼠标与圆心的偏移,拖动时计算偏移距离,超过阈值则取消吸附并恢复跟随移动
鼠标抬起时的吸附判断
- 即使拖动时没触发吸附,鼠标抬起时如果圆形在吸附范围内,也会自动吸附到交点
内容的提问来源于stack exchange,提问作者Sebastian Meckovski
相关产品推荐
相关产品推荐

