# 自定义可拖拽对象

可拖拽对象是存在于 工作区 中、可被拖拽与放置的可渲染对象。它们实现 IDraggable 接口。

通常很少需要向 Blockly 新增可拖拽对象。典型场景例如 multiselect 插件 (opens new window),或修改已有对象的拖拽行为。因为 Blockly 里可渲染对象的类型本身是固定的,工作区内只有块、气泡和工作区注释。

# 职责

可拖拽对象在执行拖拽时主要负责:

  • 把 SVG 元素移动到拖拽层。
  • 平移 SVG 元素。
  • 触发移动 事件

# 实现

要创建新的可拖拽对象,需要实现 IRenderedElementIDraggable 接口,让 Blockly 知道该对象可见且可拖拽。

class MyDraggable implements IRenderedElement, IDraggable {}

# 返回根 SVG 元素

getSvgRoot 需要返回根 SVG 元素,通常是一个 <g>,它承载该可拖拽对象的全部可视元素。

getSvgRoot() {
  return this.rootSvg;
}

# 返回可移动状态

isMovable 用于返回当前对象是否可移动。你可以临时禁用某对象的拖拽。
isMovable 返回 false,则会改为拖动工作区本身。

isMovable() {
  return true;
}

# 返回位置

getRelativeToSurfaceXY 返回一个 Coordinate,表示该对象左上角在工作区坐标系中的位置。

工作区坐标系原点位于工作区绝对左上角,不会随着工作区缩放或平移而改变。

getRelativeToSurfaceXY() {
  return this.loc;
}

# 开始拖拽

startDrag 用于初始化拖拽。它本身不移动对象,但应在这里保存拖拽中需要的数据、创建所需对象,并保存可用于 revertDrag 回滚的数据。

还应把 SVG 元素移到工作区拖拽层,确保显示在其他元素之上。
该方法会接收事件参数,可用于检查按键状态,例如区分“按住 Shift 的拖拽”和普通拖拽。

startDrag(e) {
  // 保存初始位置,供回滚使用。
  this.startLoc = this.getRelativeToSurfaceXY();

  // 为性能考虑,拖拽时先关闭工作区 resize。
  this.workspace.setResizesEnabled(false);

  // 把元素放到拖拽层。
  this.workspace.getLayerManager()?.moveToDragLayer(this);

  // 触发拖拽事件...

  // 其他初始化逻辑...
}

# 拖拽

drag 负责实际移动对象。newLoc 是工作区坐标,方法同样会收到事件参数以检查按键状态。

drag(newLoc, e) {
  this.moveTo(newLoc);
}

# 回滚拖拽

revertDrag 会把对象恢复到拖拽开始时的位置。
例如当对象被放到一个阻止移动的 DragTarget 上时,就会发生回滚。

revertDrag() {
  // 回到在 startDrag 中记录的位置。
  this.moveTo(this.startLoc);
}

# 结束拖拽

endDrag 用于清理拖拽过程,释放临时数据或对象,并把元素放回原图层。
如果调用了 revertDrag,随后一定会调用 endDrag

endDrag() {
  // 放回原始图层(此处示例为 BLOCK 层)。
  this.workspace
    .getLayerManager()
    ?.moveOffDragLayer(this, Blockly.layers.BLOCK);

  // 恢复工作区 resize。
  this.workspace.setResizesEnabled(true);
}

# 选择

当系统检测到拖拽时,真正被拖动的是“当前已选中”的元素。

# ISelectable

要让可拖拽对象可被选中,需实现 ISelectable 接口。

class MyDraggable implements ISelectable {
  constructor(workspace) {
    this.id = Blockly.utils.idGenerator.genUid();
    this.workspace = workspace;
  }

  select() {
    // 视觉上标记为已选中。
  }

  unselect() {
    // 视觉上标记为未选中。
  }
}

# 设置选中

可通过 Blockly.common.setSelected() 设置当前选中元素。通常在用户触发 pointerdown 时调用。

constructor() {
  this.initSvg();

  Blockly.browserEvents.conditionalBind(
    this.getSvgRoot(),
    'pointerdown',
    this,
    () => Blockly.common.setSelected(this));
}

# 兼容性

可拖拽对象还可以实现更多接口,以接入 Blockly 的其他系统。

# 可删除

实现 IDeletable 后,对象可被垃圾桶或其他删除目标释放。

class MyDraggable implements IDeletable {
  isDeletable() {
    return true;
  }

  dispose() {
    // 释放数据引用与 SVG 元素。
  }

  setDeleteStyle() {
    // 视觉上提示“即将被删除”。
  }
}

# 可复制

你可以实现 ICopyable 让对象可复制,再定义 IPaster 让对象可粘贴。
更多信息见 复制与粘贴