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

如何在PaperJS中为选中形状的bounding rectangle添加侧边中点手柄

如何在PaperJS中为选中形状的bounding rectangle添加中点手柄

嘿,我刚好折腾过类似的需求!PaperJS默认的选中手柄确实只有四个角,要给bounding rectangle的四边中点加上可拖拽的手柄其实不难,咱们可以通过监听选中事件、自定义手柄并处理拖拽交互来实现,下面是具体的思路和完整代码:

核心思路

其实就是三步走:

  1. 监听项目的selectionChanged事件,当选中的形状变化时,自动创建/清理中点手柄
  2. 给每个选中的形状计算出四边中点的位置,生成自定义的可拖拽手柄(比如小圆圈)
  3. 处理手柄的拖拽逻辑,让拖拽中点时能调整对应边的位置,同时同步更新手柄和形状的位置

完整代码示例

// 用来存所有中点手柄的数组,方便后续清理和更新
let midHandles = [];

// 监听选中项变化的事件,这是核心触发点
project.on('selectionChanged', function() {
  // 先把之前的旧手柄清掉,顺便移除对应的事件监听,避免内存泄漏
  midHandles.forEach(handle => {
    const targetShape = handle.data.targetShape;
    if (targetShape) {
      targetShape.off('change', updateMidHandlePositions);
    }
    handle.remove();
  });
  midHandles = [];

  const selectedShapes = project.selectedItems;
  if (selectedShapes.length === 0) return;

  selectedShapes.forEach(shape => {
    const shapeBounds = shape.bounds;
    // 计算四个中点的位置:上、右、下、左
    const midPoints = [
      new Point(shapeBounds.center.x, shapeBounds.top),
      new Point(shapeBounds.right, shapeBounds.center.y),
      new Point(shapeBounds.center.x, shapeBounds.bottom),
      new Point(shapeBounds.left, shapeBounds.center.y)
    ];

    midPoints.forEach((point, index) => {
      // 创建中点手柄,这里用蓝色小圆圈,你可以自己改样式
      const handle = new Path.Circle({
        center: point,
        radius: 4,
        fillColor: 'blue',
        // 存一下关联的形状和对应的边,拖拽时要用
        data: {
          targetShape: shape,
          side: index // 0=上边, 1=右边, 2=下边, 3=左边
        }
      });
      // 让手柄能被选中和拖拽
      handle.selected = true;
      midHandles.push(handle);
    });

    // 监听形状的变换事件(移动、缩放等),实时更新手柄位置
    shape.on('change', updateMidHandlePositions);
  });
});

// 创建一个工具来处理手柄的拖拽逻辑
const handleDragTool = new Tool();
let currentDraggingHandle = null;
let initialShapeBounds = null;

handleDragTool.on('mousedown', function(event) {
  // 检查是不是点到了咱们的中点手柄
  const hitTestResult = project.hitTest(event.point, { fill: true });
  if (hitTestResult && hitTestResult.item.data.targetShape) {
    currentDraggingHandle = hitTestResult.item;
    // 记录拖拽前的形状边界,避免拖拽时计算出错
    initialShapeBounds = currentDraggingHandle.data.targetShape.bounds.clone();
  }
});

handleDragTool.on('mousedrag', function(event) {
  if (!currentDraggingHandle) return;

  const targetShape = currentDraggingHandle.data.targetShape;
  const side = currentDraggingHandle.data.side;
  const dragDelta = event.delta;

  // 根据拖拽的边调整形状的边界
  switch (side) {
    case 0: // 拖拽上边中点,只调整上边位置
      targetShape.bounds.top = initialShapeBounds.top + dragDelta.y;
      break;
    case 1: // 拖拽右边中点,只调整右边位置
      targetShape.bounds.right = initialShapeBounds.right + dragDelta.x;
      break;
    case 2: // 拖拽下边中点,只调整下边位置
      targetShape.bounds.bottom = initialShapeBounds.bottom + dragDelta.y;
      break;
    case 3: // 拖拽左边中点,只调整左边位置
      targetShape.bounds.left = initialShapeBounds.left + dragDelta.x;
      break;
  }

  // 更新当前形状的所有中点手柄位置
  updateMidHandlePositions(targetShape);
});

handleDragTool.on('mouseup', function() {
  currentDraggingHandle = null;
  initialShapeBounds = null;
});

// 单独写一个函数来更新单个形状的中点手柄位置
function updateMidHandlePositions(shape) {
  const shapeBounds = shape.bounds;
  const shapeHandles = midHandles.filter(handle => handle.data.targetShape === shape);
  if (shapeHandles.length !== 4) return;

  // 分别更新四个手柄的位置
  shapeHandles[0].position = new Point(shapeBounds.center.x, shapeBounds.top);
  shapeHandles[1].position = new Point(shapeBounds.right, shapeBounds.center.y);
  shapeHandles[2].position = new Point(shapeBounds.center.x, shapeBounds.bottom);
  shapeHandles[3].position = new Point(shapeBounds.left, shapeBounds.center.y);
}

一些细节说明

  • 样式自定义:你可以修改手柄的radius、fillColor甚至换成其他形状(比如正方形),来匹配你的UI风格
  • 交互逻辑:拖拽中点时只会调整对应边的位置,对边保持不动,这和常见的图形编辑器逻辑一致,用户上手会很自然
  • 事件清理:每次选中变化时都会清理旧的手柄和事件监听,不会留下内存垃圾
  • 实时同步:监听形状的change事件,不管是拖拽手柄还是直接移动/缩放形状,手柄位置都会跟着同步更新

这样就能实现你想要的带中点手柄的bounding rectangle效果啦!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 08:36:29