如何用原生JS和Canvas实现可拖动并可调整大小的矩形?

来源:AI技术网作者:南京SEO公司头衔:草根站长
导读:本期聚焦于小伙伴创作的《如何用原生JS和Canvas实现可拖动并可调整大小的矩形?》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《如何用原生JS和Canvas实现可拖动并可调整大小的矩形?》有用,将其分享出去将是对创作者最好的鼓励。

在Canvas中实现可拖动并可调整大小的矩形,核心是通过监听鼠标事件来更新矩形的位置和尺寸状态,再结合Canvas的重绘机制实现交互效果。整个过程不需要依赖任何第三方库,仅用原生JS就能完成。

如何用原生JS和Canvas实现可拖动并可调整大小的矩形?

实现原理概述

要实现可交互的矩形,首先需要定义矩形的基础状态,包括位置坐标、宽度、高度,以及当前的交互状态(比如是否处于拖动中、是否处于缩放中)。然后通过监听Canvas的鼠标按下、移动、抬起事件,判断用户的操作意图,更新矩形状态后重新绘制Canvas即可。

矩形状态定义

我们需要用一个对象来存储矩形的所有状态信息,方便后续操作和修改:

// 矩形状态对象
const rect = {
  x: 100, // 矩形左上角x坐标
  y: 100, // 矩形左上角y坐标
  width: 200, // 矩形宽度
  height: 150, // 矩形高度
  isDragging: false, // 是否处于拖动状态
  isResizing: false, // 是否处于缩放状态
  dragOffsetX: 0, // 拖动时鼠标相对于矩形左上角的x偏移
  dragOffsetY: 0, // 拖动时鼠标相对于矩形左上角的y偏移
  resizeDirection: null // 缩放方向,比如'right-bottom'表示右下角缩放
};

Canvas基础绘制

首先初始化Canvas上下文,编写矩形的绘制函数,后续每次矩形状态更新后都调用这个函数重绘:

// 获取Canvas元素和上下文
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

// 设置Canvas尺寸,这里设置为800*500
canvas.width = 800;
canvas.height = 500;

// 绘制矩形的函数
function drawRect() {
  // 清空Canvas
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // 绘制矩形
  ctx.strokeStyle = '#333';
  ctx.lineWidth = 2;
  ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
  // 绘制右下角缩放手柄,方便用户识别可缩放区域
  ctx.fillStyle = '#666';
  ctx.fillRect(rect.x + rect.width - 8, rect.y + rect.height - 8, 8, 8);
}
// 初始绘制一次
drawRect();

鼠标事件监听与交互逻辑

接下来需要监听Canvas的鼠标事件,判断用户的操作是点击、拖动还是缩放,然后更新矩形状态。

鼠标按下事件处理

鼠标按下时,需要判断点击的位置是在矩形内部(触发拖动)、缩放手柄上(触发缩放),还是其他区域:

// 缩放手柄的尺寸
const resizeHandleSize = 8;

canvas.addEventListener('mousedown', (e) => {
  // 获取鼠标在Canvas中的坐标
  const mouseX = e.offsetX;
  const mouseY = e.offsetY;

  // 判断是否在右下角缩放手柄范围内
  const isInResizeHandle = mouseX >= rect.x + rect.width - resizeHandleSize &&
    mouseX <= rect.x + rect.width &&
    mouseY >= rect.y + rect.height - resizeHandleSize &&
    mouseY <= rect.y + rect.height;

  if (isInResizeHandle) {
    // 进入缩放状态
    rect.isResizing = true;
    rect.resizeDirection = 'right-bottom';
  } else if (mouseX >= rect.x && mouseX <= rect.x + rect.width &&
    mouseY >= rect.y && mouseY <= rect.y + rect.height) {
    // 进入拖动状态,计算鼠标相对于矩形左上角的偏移
    rect.isDragging = true;
    rect.dragOffsetX = mouseX - rect.x;
    rect.dragOffsetY = mouseY - rect.y;
  }
});

鼠标移动事件处理

鼠标移动时,根据当前的交互状态更新矩形的位置或尺寸:

canvas.addEventListener('mousemove', (e) => {
  const mouseX = e.offsetX;
  const mouseY = e.offsetY;

  // 拖动状态处理
  if (rect.isDragging) {
    // 更新矩形左上角坐标,减去偏移量保证鼠标和矩形的相对位置不变
    rect.x = mouseX - rect.dragOffsetX;
    rect.y = mouseY - rect.dragOffsetY;
    // 限制矩形不超出Canvas边界
    rect.x = Math.max(0, Math.min(rect.x, canvas.width - rect.width));
    rect.y = Math.max(0, Math.min(rect.y, canvas.height - rect.height));
    // 重绘
    drawRect();
  }

  // 缩放状态处理
  if (rect.isResizing) {
    // 右下角缩放,更新宽度和高度,最小尺寸限制为20
    rect.width = Math.max(20, mouseX - rect.x);
    rect.height = Math.max(20, mouseY - rect.y);
    // 限制矩形不超出Canvas边界
    if (rect.x + rect.width > canvas.width) {
      rect.width = canvas.width - rect.x;
    }
    if (rect.y + rect.height > canvas.height) {
      rect.height = canvas.height - rect.y;
    }
    // 重绘
    drawRect();
  }
});

鼠标抬起事件处理

鼠标抬起时,需要重置交互状态,结束拖动或缩放:

canvas.addEventListener('mouseup', () => {
  rect.isDragging = false;
  rect.isResizing = false;
  rect.resizeDirection = null;
});

// 鼠标移出Canvas时也结束交互,避免状态异常
canvas.addEventListener('mouseleave', () => {
  rect.isDragging = false;
  rect.isResizing = false;
  rect.resizeDirection = null;
});

完整示例代码

将上面的代码整合起来,就是完整的实现代码,你可以直接复制到HTML文件中运行:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Canvas可拖动可缩放矩形</title>
  <style>
    body {
      margin: 0;
      padding: 20px;
      font-family: Arial, sans-serif;
    }
    #canvas {
      border: 1px solid #ccc;
      background-color: #f9f9f9;
    }
  </style>
</head>
<body>
  <h3>Canvas可拖动可调整大小的矩形</h3>
  <p>点击矩形内部拖动,点击右下角小方块调整大小</p>
  <canvas id="canvas"></canvas>

  <script>
    const rect = {
      x: 100,
      y: 100,
      width: 200,
      height: 150,
      isDragging: false,
      isResizing: false,
      dragOffsetX: 0,
      dragOffsetY: 0,
      resizeDirection: null
    };

    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = 800;
    canvas.height = 500;
    const resizeHandleSize = 8;

    function drawRect() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.strokeStyle = '#333';
      ctx.lineWidth = 2;
      ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
      ctx.fillStyle = '#666';
      ctx.fillRect(rect.x + rect.width - resizeHandleSize, rect.y + rect.height - resizeHandleSize, resizeHandleSize, resizeHandleSize);
    }
    drawRect();

    canvas.addEventListener('mousedown', (e) => {
      const mouseX = e.offsetX;
      const mouseY = e.offsetY;
      const isInResizeHandle = mouseX >= rect.x + rect.width - resizeHandleSize &&
        mouseX <= rect.x + rect.width &&
        mouseY >= rect.y + rect.height - resizeHandleSize &&
        mouseY <= rect.y + rect.height;

      if (isInResizeHandle) {
        rect.isResizing = true;
        rect.resizeDirection = 'right-bottom';
      } else if (mouseX >= rect.x && mouseX <= rect.x + rect.width &&
        mouseY >= rect.y && mouseY <= rect.y + rect.height) {
        rect.isDragging = true;
        rect.dragOffsetX = mouseX - rect.x;
        rect.dragOffsetY = mouseY - rect.y;
      }
    });

    canvas.addEventListener('mousemove', (e) => {
      const mouseX = e.offsetX;
      const mouseY = e.offsetY;

      if (rect.isDragging) {
        rect.x = mouseX - rect.dragOffsetX;
        rect.y = mouseY - rect.dragOffsetY;
        rect.x = Math.max(0, Math.min(rect.x, canvas.width - rect.width));
        rect.y = Math.max(0, Math.min(rect.y, canvas.height - rect.height));
        drawRect();
      }

      if (rect.isResizing) {
        rect.width = Math.max(20, mouseX - rect.x);
        rect.height = Math.max(20, mouseY - rect.y);
        if (rect.x + rect.width > canvas.width) {
          rect.width = canvas.width - rect.x;
        }
        if (rect.y + rect.height > canvas.height) {
          rect.height = canvas.height - rect.y;
        }
        drawRect();
      }
    });

    canvas.addEventListener('mouseup', () => {
      rect.isDragging = false;
      rect.isResizing = false;
      rect.resizeDirection = null;
    });

    canvas.addEventListener('mouseleave', () => {
      rect.isDragging = false;
      rect.isResizing = false;
      rect.resizeDirection = null;
    });
  </script>
</body>
</html>

扩展优化方向

上面的示例实现了基础功能,还可以根据需求做更多扩展:

  • 增加更多缩放手柄,比如左上角、右上角、左下角,实现多方向的缩放
  • 添加矩形选中状态,只有选中时才显示缩放手柄,避免干扰
  • 增加键盘辅助操作,比如按住Shift键等比例缩放
  • 支持多个矩形的交互,需要维护矩形数组和当前选中矩形的索引

Canvas原生JS矩形拖动矩形缩放鼠标事件修改时间:2026-07-21 05:18:34

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。