解码cornerstoneTools事件系统:鼠标与触摸事件如何从Listener流转到工具
【免费下载链接】cornerstoneToolsA framework for tools built on top of Cornerstone.项目地址: https://gitcode.com/gh_mirrors/co/cornerstoneTools
cornerstoneTools 是构建在 Cornerstone 医学影像引擎之上的工具框架,它的事件系统采用「Listener → Dispatcher → 工具回调」的三级流水线:浏览器原生鼠标/触摸事件先被**事件监听器(Listener)归一化为带坐标信息的自定义事件,再由事件分发器(Dispatcher)**筛选出当前激活的工具,最终调用工具的回调函数完成画测量、拖拽手柄、平移缩放等交互。搞懂这条事件流转链路,你就能轻松定制自己的医学影像交互工具。
🧭 一句话看懂:事件如何一步步流向工具
整个流转过程可以压缩成一条链路:
浏览器原生事件 (mousedown / touchstart ...) ↓ ① Listener:坐标转换 + 归一化 cornerstonetools 前缀的自定义事件 (cornerstonetoolsmousedown ...) ↓ ② Dispatcher:按工具模式/按键/手柄位置筛选 工具回调 (mouseMoveCallback / mouseDragCallback / handleSelectedCallback ...)所有自定义事件名都在 events.js 中集中定义,统一加cornerstonetools前缀,避免与原生事件冲突,也方便跨浏览器保持一致行为。
① Listener 层:把原始输入变成"标准数据包"
Listener 层是事件系统的入口,代码位于src/eventListeners/目录。
鼠标事件:mouseEventListeners
以鼠标按下为例,mousedown触发后 mouseEventListeners.js 会做三件事:
- 坐标转换:把 DOM 事件同时换算成
page(页面)、image(图像像素)、client(视口)、canvas(画布)四套坐标系,供不同类型工具按需取用; - 构造标准 eventData:包含
startPoints、currentPoints、lastPoints、deltaPoints、buttons、viewport等字段; - 派发自定义事件:通过 triggerEvent.js 创建一个
cancelable的CustomEvent,派发到影像元素上。
💡 一个关键设计:如果MOUSE_DOWN事件没有被任何工具消费(即dispatchEvent返回true),Listener 会紧接着再派发展示"激活"语义的MOUSE_DOWN_ACTIVATE事件——这正是大多数标注工具创建新测量的钩子。按下已有手柄 vs 按下空白处,由此天然区分。
拖拽期间,mousemove/mouseup被临时挂到document上,保证鼠标移出画布也能持续追踪(见 mouseEventListeners.js)。
触摸事件:Hammer.js 识别手势
触摸侧的 touchEventListeners.js 用Hammer.js把原始 touch 输入识别为tap、pan、pinch、rotate、doubletap等手势,再统一映射为TOUCH_DRAG、TOUCH_PINCH、TOUCH_ROTATE、TAP、DOUBLE_TAP等事件。
它还有几个贴心的细节:
- 长按判定:手指停留 700ms 且位移小于 5px 才触发
TOUCH_PRESS; - 防"鬼点":触摸事件会
preventDefault()阻止浏览器补发模拟鼠标事件,另有 preventGhostClick.js 兜底; - 同样的激活机制:
TOUCH_START未被消费时,会派发TOUCH_START_ACTIVE,与鼠标侧的MOUSE_DOWN_ACTIVATE语义对齐。
② Dispatcher 层:为事件挑选"接活的工具"
自定义事件派发出后,由 Dispatcher 接住并分发给正确的工具。
注册表:谁监听哪些事件
mouseToolEventDispatcher.js 一次性注册 8 个自定义事件监听器(MOUSE_MOVE、MOUSE_DOWN、MOUSE_DOWN_ACTIVATE、MOUSE_DRAG、MOUSE_UP、MOUSE_CLICK、MOUSE_DOUBLE_CLICK、MOUSE_WHEEL)。触摸侧的 touchToolEventDispatcher.js 则注册TAP、TOUCH_START、TOUCH_DRAG、TOUCH_PINCH、TOUCH_ROTATE等 10 个监听器。
每个 Dispatcher 内部只是"路由表",真正的判断逻辑在mouseEventHandlers/与touchEventHandlers/各自独立的小模块里。
鼠标按下的决策链:一次事件,四道关卡
mouseDown.js 是最能体现事件系统精髓的文件。它从全局 store 取出该元素上所有可交互工具,按优先级依次尝试:
| 顺序 | 关卡 | 对应工具回调 | 典型场景 |
|---|---|---|---|
| 1 | 活跃工具的预处理 | preMouseDownCallback | 工具抢先消费特殊手势 |
| 2 | 是否点在可移动手柄附近 | handleSelectedCallback | 拖拽矩形 ROI 的角点 |
| 3 | 是否点在已有标注附近 | toolSelectedCallback | 选中整条测量线 |
| 4 | 活跃工具的后处理 | postMouseDownCallback | 兜底逻辑 |
任何一关命中即返回,事件不会继续往下走——这就是"事件可被取消/阻止冒泡"的含义。若四关都没命中,事件就留给MOUSE_DOWN_ACTIVATE阶段去创建新测量。
其他高频事件各有所司
mouseMove:主要用于更新工具的悬停高亮状态,命中手柄或标注线就重绘(见 mouseMove.js);mouseDrag:只把事件交给**当前活跃且匹配鼠标按键掩码(mouseButtonMask)**的第一个工具,调用其mouseDragCallback,实现平移、缩放、wwwc 调节等(见 mouseDrag.js)。
🛠️ 工具视角:你需要实现的回调清单
理解了流转链路,写自定义工具时其实只需关注几个回调入口:
mouseMoveCallback—— 鼠标悬停(高亮提示)preMouseDownCallback/postMouseDownCallback—— 按下前/后钩子handleSelectedCallback/toolSelectedCallback—— 选中手柄 / 整体标注mouseDragCallback—— 拖拽进行中- 触摸侧对应的
touchStartCallback、touchDragCallback等
工具的状态(active / passive / disabled)、按键绑定都存在src/store/与 toolState.js 中,Dispatcher 每次分派前都会从这里读取最新状态,因此切换工具模式后下一次事件立即生效。
🔍 遇到问题怎么调试?
- 打开项目的调试文档:docs/latest/debugging/index.md;
- 事件监听器内部使用 logger 输出每次按键(如
logger.log('mousedown: %o', ...)),控制台可直接观察事件是否到达; - 完整事件名枚举见 events.js,也可自行
element.addEventListener('cornerstonetoolsmousedown', ...)旁路监听任意阶段。
📌 小结
cornerstoneTools 事件系统的设计核心就三点:
- Listener 归一化——四套坐标 + 标准 eventData,抹平浏览器差异;
- Dispatcher 分派——按工具模式、按键掩码、空间位置(手柄/标注/空白)逐级筛选;
- 回调即扩展点——工具只需实现约定的回调函数,无需关心底层事件细节。
沿着src/eventListeners/→src/eventDispatchers/→src/tools/这条路径阅读源码,你就能把任何新的交互手势接入 cornerstoneTools 的工具体系。
【免费下载链接】cornerstoneToolsA framework for tools built on top of Cornerstone.项目地址: https://gitcode.com/gh_mirrors/co/cornerstoneTools
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考