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

如何在Canvas上实现可拖拽移动选区及近距离Hover检测

实现Canvas选区的Hover检测与拖拽移动

要实现鼠标在选区附近几像素范围内触发Hover检测,同时支持拖拽移动选区的需求,我们可以通过记录选区边界数据+容错碰撞检测+状态管理来完成。以下是基于你提供的代码修改后的完整实现方案:

核心逻辑

  • 保存已完成选区的标准化坐标(左上角x/y、宽高w/h)
  • 鼠标移动时,判断当前位置是否在选区的「容错扩展边界」内(比如±5像素)
  • 通过状态变量区分「创建新选区」和「移动已有选区」两种操作,避免冲突

完整实现代码

HTML

<img id="img" src="https://i.imgur.com/okSIKkW.jpg">
<div id="canvas-container">
<canvas id="c1" height="200" width="200"></canvas>
<canvas id="c2" height="200" width="200"></canvas>
</div>

CSS

#img { display: none; }
#canvas-container { position: relative; }
canvas { position: absolute; left: 0; top: 0; }
#c1 { z-index: 0; }
#c2 { z-index: 1; }

JavaScript

const c1 = document.getElementById("c1"), c2 = document.getElementById("c2");
const ctx1 = c1.getContext("2d"), ctx2 = c2.getContext("2d");
ctx2.setLineDash([5, 5]);

// 状态与数据管理
let origin = null; // 创建选区的起点
let currentSelection = null; // 已完成的选区数据:{x, y, w, h}
let isDraggingSelection = false; // 是否处于拖动选区状态
let dragOffset = null; // 拖动时鼠标相对选区左上角的偏移量
const HOVER_TOLERANCE = 5; // Hover检测的容错像素范围

window.onload = () => { ctx1.drawImage(document.getElementById("img"), 0, 0); };

c2.onmousedown = e => {
  const mousePos = {x: e.offsetX, y: e.offsetY};
  
  // 优先判断是否点击在已有选区的容错范围内
  if (currentSelection && isHoveringSelection(mousePos)) {
    isDraggingSelection = true;
    // 计算鼠标与选区左上角的偏移值
    dragOffset = {
      x: mousePos.x - currentSelection.x,
      y: mousePos.y - currentSelection.y
    };
    return;
  }
  
  // 否则创建新选区,清除旧选区
  origin = mousePos;
  currentSelection = null;
};

window.onmouseup = e => {
  if (origin) {
    // 完成选区创建,标准化坐标(确保x/y为左上角,w/h为正)
    const endPos = {x: e.offsetX, y: e.offsetY};
    currentSelection = normalizeRect(origin, endPos);
    redrawSelection();
  }
  origin = null;
  isDraggingSelection = false;
};

c2.onmousemove = e => {
  const mousePos = {x: e.offsetX, y: e.offsetY};
  
  if (isDraggingSelection) {
    // 更新选区位置并重绘
    currentSelection.x = mousePos.x - dragOffset.x;
    currentSelection.y = mousePos.y - dragOffset.y;
    redrawSelection();
    return;
  }
  
  if (origin) {
    // 绘制正在创建的临时选区
    ctx2.clearRect(0, 0, c2.width, c2.height);
    ctx2.strokeStyle = "#ff0000";
    ctx2.beginPath();
    const tempRect = normalizeRect(origin, mousePos);
    ctx2.rect(tempRect.x, tempRect.y, tempRect.w, tempRect.h);
    ctx2.stroke();
    return;
  }
  
  // 根据Hover状态切换鼠标样式
  c2.style.cursor = currentSelection && isHoveringSelection(mousePos) 
    ? "move" 
    : "default";
};

// 辅助函数:标准化矩形坐标,避免拖拽方向导致的坐标混乱
function normalizeRect(start, end) {
  return {
    x: Math.min(start.x, end.x),
    y: Math.min(start.y, end.y),
    w: Math.abs(end.x - start.x),
    h: Math.abs(end.y - start.y)
  };
}

// 辅助函数:判断鼠标是否在选区的容错范围内
function isHoveringSelection(mousePos) {
  const {x, y, w, h} = currentSelection;
  // 检测范围为选区边界向外扩展HOVER_TOLERANCE像素
  return mousePos.x >= x - HOVER_TOLERANCE &&
         mousePos.x <= x + w + HOVER_TOLERANCE &&
         mousePos.y >= y - HOVER_TOLERANCE &&
         mousePos.y <= y + h + HOVER_TOLERANCE;
}

// 辅助函数:重绘选区
function redrawSelection() {
  ctx2.clearRect(0, 0, c2.width, c2.height);
  ctx2.strokeStyle = "#ff0000";
  ctx2.beginPath();
  ctx2.rect(currentSelection.x, currentSelection.y, currentSelection.w, currentSelection.h);
  ctx2.stroke();
}

关键细节说明

  • 选区坐标标准化:通过normalizeRect统一处理不同拖拽方向的选区坐标,确保后续检测和移动逻辑一致
  • 容错Hover检测:isHoveringSelection给选区添加了指定像素的扩展检测范围,让鼠标靠近选区时就能触发响应
  • 操作状态区分:用isDraggingSelection变量隔离创建选区和移动选区的逻辑,避免操作冲突
  • 视觉反馈:Hover选区时鼠标变为移动样式,提升用户操作感知

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 04:45:32