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

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();

关键逻辑说明

  1. 吸附范围与拖拽阈值

    • SNAP_RADIUS:定义圆形进入多大范围时触发吸附,可根据需求调整
    • SNAP_DRAG_THRESHOLD:吸附后需要拖动的最小距离,超过这个距离才会取消吸附,实现"更大力度拖动"的效果
  2. 交点坐标计算

    • 直接根据两条线的参数计算交点:水平中线y=height/2和垂直线x=width/6的交点就是(width/6, height/2)
  3. 鼠标坐标转换

    • 使用canvas.getBoundingClientRect()将鼠标的客户端坐标转换为Canvas内部的相对坐标,避免Canvas在页面中有偏移时出现坐标错误
  4. 吸附状态管理

    • isSnapped:标记圆形是否处于吸附状态
    • 当圆形进入吸附范围时,自动移动到交点并标记为吸附状态
    • 吸附状态下点击圆形,记录鼠标与圆心的偏移,拖动时计算偏移距离,超过阈值则取消吸附并恢复跟随移动
  5. 鼠标抬起时的吸附判断

    • 即使拖动时没触发吸附,鼠标抬起时如果圆形在吸附范围内,也会自动吸附到交点

内容的提问来源于stack exchange,提问作者Sebastian Meckovski

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 11:41:04