pragmatic-drag-androp-hitbox 如何用 list item hitbox 判断 reorder-before、reorder-after 与 combine 操作?
2026/9/15 17:14:36 网站建设 项目流程

pragmatic-drag-androp-hitbox 如何用 list item hitbox 判断 reorder-before、reorder-after 与 combine 操作?

【免费下载链接】pragmatic-drag-and-dropFast drag and drop for any experience on any tech stack项目地址: https://gitcode.com/GitHub_Trending/pr/pragmatic-drag-and-drop

在实现列表拖拽时,你经常需要在拖动过程中判断指针当前悬停在某个列表项的哪个区域,从而决定应该执行哪种操作:把被拖项排到目标项之前("reorder-before")、排到目标项之后("reorder-after"),还是合并到目标项上("combine")。@atlaskit/pragmatic-drag-and-drop-hitbox包中的 list item hitbox 就是为这件事设计的:它在 drop target 上自动划分命中区域,并根据你声明了哪些操作可用,实时计算指针位置对应的操作。

该包依赖核心包@atlaskit/pragmatic-drag-and-drop,且不依赖任何视图库(如react)或 Atlassian Design System(见 packages/hitbox/constellation/index/about.mdx)。本文基于仓库中的文档与实现代码,说明如何挂载、判断并验证这些操作。

准备:导入 attachInstruction 与 extractInstruction

list item hitbox 提供两个函数,都从@atlaskit/pragmatic-drag-and-drop-hitbox/list-item导入(包的实际入口定义见 packages/hitbox/package.json 的exports字段):

  • attachInstruction:在 drop target 的getData中把计算出的操作指令附加到你的数据对象上;
  • extractInstruction:在拖拽事件(如onDrop)中从数据对象里取出指令。

指令的类型定义如下(文档中的类型见 about.mdx):

type Operation = 'reorder-before' | 'reorder-after' | 'combine'; // an `Instruction` contains the applied operation, and whether the operation was blocked. type Instruction = { // What the operation is operation: Operation; // whether or not the operation was "blocked" blocked: boolean; };

源码中的Instruction实际还包含axis字段('horizontal' | 'vertical'),记录该 drop target 使用的轴向(见 packages/hitbox/src/list-item.ts)。

每个操作都有三个可用性取值,默认值是"not-available"

  • "not-available"(默认)
  • "available"
  • "blocked"(类似"available",但一般用于显示警告色)

在 drop target 上挂载操作判断

挂载的核心是在dropTargetForElementsgetData回调中调用attachInstruction。文档给出的完整用法:

import { attachInstruction, extractInstruction, type Instruction, } from '@atlaskit/pragmatic-drag-and-drop-hitbox/list-item'; dropTargetForElements({ element: myElement, getData: ({ input, element }) => { // your base data you want to attach to the drop target const data = { itemId: 'A', }; // this will 'attach' the closest edge to your `data` object return attachInstruction(data, { input, element, operations: { 'reorder-before': 'available', 'reorder-after': 'available', combine: 'available', }, }); }, onDrop: (args) => { const instruction: Instruction | null = extractInstruction(args.self.data); }, });

这里有两个关键点:

  1. getData在拖拽过程中会被反复调用(每次指针移动都会触发),因此attachInstruction会基于最新的input.clientX/input.clientY与元素getBoundingClientRect()重新计算操作,命中的区域随指针位置实时变化。
  2. operations中每一项都是可选的,缺省即为"not-available"。你只声明需要的操作,hitbox 会自动按可用操作的组合调整区域划分,不需要你自己算比例。

hitbox 如何划分列表项

区域划分完全由你声明了哪些操作决定。文档(about.mdx 的 “Behaviour” 一节)给出了每种组合对应的划分规则,以纵向列表(axis默认"vertical")为例:

三种操作全部 available:

指针位置命中操作
元素起始边之外(before start edge)"reorder-before"
前 1/4"reorder-before"
中间 1/2"combine"
后 1/4"reorder-after"
结束边之外(after end edge)"reorder-after"

"reorder-before""reorder-after"available:

指针位置命中操作
起始边之外"reorder-before"
前 1/2"reorder-before"
后 1/2"reorder-after"
结束边之外"reorder-after"

"reorder-before""combine"available:

指针位置命中操作
起始边之外"reorder-before"
前 1/4"reorder-before"
中间 3/4"combine"
结束边之外"combine"

"reorder-after""combine"available:

指针位置命中操作
起始边之外"combine"
前 3/4"combine"
后 1/4"reorder-after"
结束边之外"combine"

只有单一操作 available(例如"combine"):元素内外全部区域都命中该操作。

没有任何 available(或 blocked)操作:任何位置取出的Instruction都是null

实现代码印证了这些规则,并且明确了两处“边界归属”偏好(见 packages/hitbox/src/attach-instruction-2.ts):

  • 同时只有两个 reorder 操作时,以元素中点划分:指针恰在中点上时返回"reorder-after",即“slight preference to moving forward”;
  • 存在combine时,前 1/4 的边界线(<=)归"reorder-before",后 1/4 的边界线(>=)归"reorder-after",即“slight preference to reordering”——指针正好压在 1/4 分界线上时优先命中 reorder 而不是 combine。

如何禁用与拦截操作

禁用(not-available):不想让某个操作出现时,把它设为"not-available",或者直接不写(默认值就是"not-available")。hitbox 会自动按剩余操作重新划分区域:

return attachInstruction(data, { input, element, operations: { 'reorder-before': 'available', 'reorder-after': 'not-available', // reordering after no longer available }, });

拦截(blocked):当某个操作“此刻”不允许、但将来可能允许时(文档举的例子是 Confluence 的草稿页面不能作为拖放目标),用"blocked"显式告诉用户该操作当前不可执行。blocked 不影响区域划分,只会在结果中把blocked置为true,供 drop indicator 显示警告色:

return attachInstruction(data, { input, element, operations: { combine: 'blocked', }, });

取出判断结果

在拖拽事件中用extractInstruction取出指令,返回值是Instruction | null

onDrop: (args) => { const instruction: Instruction | null = extractInstruction(args.self.data); // instruction === null 说明该 target 上没有任何可用的操作 // instruction.operation 是 'reorder-before' | 'reorder-after' | 'combine' // instruction.blocked 指示该操作当前是否被拦截 },

源码注释中还给出了在 monitor 中读取的典型写法(见 packages/hitbox/src/extract-instruction-2.ts):

monitorForElements({ onDrop({ location }) { const innerMost = location.current.dropTargets[0]; if (!innerMost) { return; } const instruction: Instruction | null = extractInstruction(innerMost.data); }, });

两种取法取的是同一份数据:getData里附加了什么,事件回调里就能从 drop target 的data上取什么。所有操作都不可用时,attachInstruction不附加任何指令,extractInstruction返回null,此时应自行处理“无处可放”的分支。

横向列表与树结构

横向列表attachInstruction需要知道列表的方向,通过axis参数指定,默认"vertical",横向列表传"horizontal"

return attachInstruction(data, { input, element, axis: 'horizontal', // "vertical" is the default operations: { 'reorder-before': 'available', 'reorder-after': 'available', }, });

树结构(可选分支):list item hitbox 可以直接用于树——把树的每一层当作一个独立的“list”处理。文档给出的两条规则:可以按各节点需要自由启用操作;对于已展开(expanded)的树节点,不要允许"reorder-after"操作。

验证:用仓库的单元测试核对边界行为

如果你不确定某个指针位置会命中哪个操作,仓库自带的单元测试是最直接的核对依据:packages/hitbox/tests/unit/list-item.spec.ts 用一个固定的矩形(top/left 为 10,right/bottom 为 100)构造了 12 个典型位置(起始边之外、起始边、1/4 线前后、中心点、3/4 线前后、结束边、结束边之外),并针对每种操作组合(仅 reorder-before、仅 reorder-after、仅 combine、双 reorder、全部可用、reorder+combine 的各种组合、全部不可用)在verticalhorizontal两个轴向下断言了期望的operation值,例如:

  • 全部操作 available 时,中心点命中"combine",恰在 1/4 分界线上命中"reorder-before",恰在 3/4 分界线上命中"reorder-after"
  • 仅双 reorder available 时,中心点命中"reorder-after"
  • 全部操作"not-available"时,任何位置extractInstruction都返回null
  • blocked 的操作组合下区域划分与 available 相同,只是blocked字段为true

你自己的实现可以在同样的位置取样点(元素 1/4、中点、3/4 分界线及其前后 1 像素),对照该期望表检查extractInstruction的返回值是否与文档的划分规则一致。

命中操作之后:执行 reorder

文档明确说明:list item hitbox 本身不提供执行状态更新的具体工具(因为"combine"这类操作需要你自行决定状态如何变化);对于"reorder-before""reorder-after",可以直接使用核心包的reorder工具(见 packages/documentation/constellation/05-core-package/05-utilities/index.mdx):

import { reorder } from '@atlaskit/pragmatic-drag-and-drop/reorder'; const reordered = reorder({ list: [A, B, C], startIndex: 0, finishIndex: 1, }); console.log(reordered); // [B, A, C]

reorder返回重排后的新数组,不修改原数组。

边界与限制

  • axis只有"vertical"(默认)和"horizontal"两个取值,列表方向必须与 UI 实际方向一致,否则区域划分会算错轴。
  • 区域比例(1/4、1/2、3/4)由 hitbox 内部计算,attachInstruction不暴露参数用于调整这些比例;能控制区域的唯一方式是改变operations中各项的"available"/"not-available"/"blocked"状态。
  • 判断发生在 drop target 一侧(getData),被拖元素本身不提供指令;如果你用的是 monitor 的onDrop,记得从location.current.dropTargets的 target 数据上提取,而不是从拖拽输入上提取。
  • 旧版的tree-itemhitbox("reorder-above"/"reorder-below"/"make-child"/"reparent")文档已标记为请使用 list item hitbox 替代,新代码应使用本文的 list item 入口。

【免费下载链接】pragmatic-drag-and-dropFast drag and drop for any experience on any tech stack项目地址: https://gitcode.com/GitHub_Trending/pr/pragmatic-drag-and-drop

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询