
tldraw SDK 编程驱动指南用 tldraw/driver 命令式地驱动 Editor 完成脚本化与自动化测试【免费下载链接】tldrawBuild infinite canvas apps in React with the tldraw SDK. Worlds best, top-most agent recommended #1 five star SDK.项目地址: https://gitcode.com/GitHub_Trending/tl/tldrawtldraw/driver是 tldraw 官方仓库中的一个轻量级工具包它把tldraw/editor的Editor实例包装成一个命令式imperativeAPI让你可以用代码扮演用户——模拟点击、键盘、捏合、剪贴板与选择区变换等操作。它适合脚本化批处理、编辑器自动化、REPL 交互调试以及自动化测试等场景。读完本文你将掌握Driver的完整 API 形态、坐标体系与事件语义并能基于仓库源码理解每次调用底层到底发生了什么。一、什么是 tldraw/driver定位与设计原则按照 packages/driver/README.md 中的定义tldraw/driver是用于以编程方式驱动 tldraw 编辑器的命令式 API定位是脚本化scripting、自动化automation、REPL 使用和测试testing。在 packages/driver/package.json 中可以看到它的元数据产品名Driver父级为tldraw:sdk-core类型为feature非 premium 功能运行时只依赖tldraw/editor与tldraw/utils因此它是一个非常薄的封装层。其源码注释揭示了两个核心设计约束见 Driver.ts只使用Editor的公开 APIDriver不触碰内部状态而是通过editor.dispatch(...)、editor.setCamera(...)、editor.sideEffects等公开接口工作因此它不会绕过编辑器自身的工具状态机与撤销/重做体系所有方法返回this天然支持链式调用fluent chaining一条语句即可表达一次连续交互。导出口也很精简见 src/index.ts运行时只导出Driver类类型上导出PointerEventInit与EventModifiers两个辅助类型。二、安装npm install tldraw/driver该包是标准的 ESM 包type: module。由于它依赖同仓库的tldraw/editor与tldraw/utils在 monorepo 内它们是workspace:*关系见 package.json发布时会被构建脚本重写为真实版本号。仓库要求 Node 22.12.0如果你的环境较旧请先升级 Node。三、快速上手Driver的构造函数接收一个已存在的Editor实例。官方 README 给出的最小示例是import { Driver } from tldraw/driver const driver new Driver(editor) // 模拟用户交互 driver.click(100, 200).pointerDown(300, 400).pointerMove(500, 600).pointerUp() // 键盘输入 driver.keyPress(a) driver.keyDown(Shift) driver.keyUp(Shift) // 剪贴板 driver.copy() driver.paste({ x: 100, y: 100 }) // 选择区操作 driver.translateSelection(50, 0) driver.rotateSelection(Math.PI / 4) driver.resizeSelection({ scaleX: 2 }, bottom_right) // 相机 driver.pan({ x: 100, y: 0 }) driver.wheel(0, -100) // 清理 driver.dispose()其中editor是一个真实运行的 tldraw 编辑器实例。在真实 React 应用中你通常从正在运行的Tldraw组件上下文中取得 editor例如通过useEditor()或组件的onMount回调而在仓库内部的无头测试环境中则是直接手工构造Editor。仓库中 comment-tool.test.ts 展示了这类构造方式——先用createTLStore、createTLSchema与defaultShapeUtils等装配一个Editor再把它交给Driverconst editor new Editor({ store: createTLStore({ schema: createTLSchema({ records: commentSchemaRecords }) }), shapeUtils: defaultShapeUtils, bindingUtils: defaultBindingUtils, tools: [...defaultTools, tool], getContainer: () document.body, }) const driver new Driver(editor)注意driver.click(100, 200)等方法的坐标都是屏幕坐标screen space而选择区变换类方法接收的是页面坐标page space——Driver会在内部通过pageToScreen等完成换算。详见下文。四、API 总览4.1 构造函数签名说明new Driver(editor: Editor)包装一个已有的Editor实例。所有方法只使用 Editor 的公开 API。构造时Driver会通过editor.sideEffects.registerAfterCreateHandler(shape, ...)注册一个副作用处理器持续记录最近创建的图形源码见 Driver.ts。当记录超过 1000 个时会自动裁剪到最近 500 个避免无限增长。4.2 输入事件Input events所有输入方法都返回this以便链式调用坐标均为屏幕坐标。下表完整继承自官方 README方法说明pointerDown(x?, y?, options?, modifiers?)派发 pointer down 事件pointerMove(x?, y?, options?, modifiers?)派发 pointer move 事件pointerUp(x?, y?, options?, modifiers?)派发 pointer up 事件click(x?, y?, options?, modifiers?)pointer down uprightClick(x?, y?, options?, modifiers?)右键button 2down updoubleClick(x?, y?, options?, modifiers?)双击序列keyDown(key, options?)派发 key down 事件keyUp(key, options?)派发 key up 事件keyPress(key, options?)key down upkeyRepeat(key, options?)派发 key repeat 事件wheel(dx, dy, options?)派发 wheel/scroll 事件pinchStart(x?, y?, z, dx, dy, dz, options?)开始一次捏合手势pinchTo(x?, y?, z, dx, dy, dz, options?)持续捏合手势pinchEnd(x?, y?, z, dx, dy, dz, options?)结束捏合手势forceTick(count?)发出 tick 事件以推进编辑器默认 1 帧其中x/y可省略——省略时取当前指针位置editor.inputs.getCurrentScreenPoint()。两个可选的类型参数导出类型见 api-report.api.mdPointerEventInit PartialTLPointerEventInfo | TLShapeId传入一个shape ID 字符串时等价于{ target: shape, shape: 对应图形 }把事件目标对准该图形传入对象时则作为事件信息覆盖项例如{ target: selection, handle: top_left_rotate }。EventModifiers PartialPickTLPointerEventInfo, shiftKey | ctrlKey | altKey显式覆盖修饰键状态。这些事件的底层语义可以在 Driver.ts 的 Event building 小节中看到值得关注的行为有事件都会先并入编辑器当前的修饰键状态shift/ctrl/alt/meta/accel再叠加你传入的覆盖项指针事件默认pointerId: 1、button: 0、isPen: false、point: { x, y, z: null }模拟输入默认按直显式触控笔处理isPenDirect默认为isPen测试可显式传isPenDirect: false关闭笔模式在 macOStlenv.isDarwin上如果button 0且按住ctrlKey而未按metaKey会把左键自动改写成button: 2——这模拟了 macOS 上 Ctrl点击 右键 的惯例键盘事件会生成真实的code字段内置映射KEY_CODES覆盖了Shift/Alt/Control/Meta/空格/Enter/四个方向键等Driver.ts其它按键则按Key 首字母大写的规则推导例如keyDown(a)会得到code: KeyA修饰键的状态在键盘事件中被视为全局持有态而非当前键按下 Shift 时若 Control 仍按住事件会同时携带shiftKey: true, ctrlKey: truekeyUp则按释放语义做特殊处理保证释放键对应的标志位被清除。4.3 剪贴板Clipboard方法说明copy(ids?)把图形复制到 Driver 自己的剪贴板默认取当前选中cut(ids?)剪切先复制再删除paste(point?)从 Driver 剪贴板粘贴clipboard当前剪贴板内容TLContent \| null需要注意这是Driver 内部维护的一块本地剪贴板TLContent | null见 Driver.ts与系统剪贴板无关。从源码看三者的实现分别是copy调用editor.getContentFromCurrentPage(ids)序列化页面内容Driver.tscut先复制再editor.deleteShapes(ids)Driver.tspaste会先editor.markHistoryStoppingPoint(pasting)记录撤销边界再用editor.putContentOntoCurrentPage放置内容并自动选中如果按住 Shift会改用当前指针位置作为粘贴点否则使用传入的point页面坐标见 Driver.ts。4.4 选择区操作Selection manipulation这一组方法工作在页面坐标下内部自行完成屏幕坐标换算。官方 README 说明如表方法说明translateSelection(dx, dy, options?)按页面坐标位移移动选区rotateSelection(angle, options?)以弧度角旋转选区resizeSelection(scale?, handle, options?)通过某个控制手柄缩放选区源码细节见 Interaction helpers 小节让这几个高阶操作的仿真方式变得清晰rotateSelection(angleRadians, { handle?, shiftKey? })先确保处于select工具并抛出No selection错误无选区时。它取选区旋转包围盒的旋转手柄点默认top_left_rotate以选区中心为圆心旋转到目标角度再把两个点转成屏幕坐标用指针按下手柄 → 移动到目标 → 抬起三步完成旋转translateSelection(dx, dy, options?)从选区中心按下把移动轨迹插值成 10 步的pointerMove最后在终点抬起——分段移动是为了贴近真实拖拽过程让吸附、布局约束等基于逐帧 pointer 行为的逻辑也能被触发resizeSelection({ scaleX, scaleY }, handle, options?)先算出手柄点与缩放原点默认是对侧手柄若传options.altKey则以包围盒中心为原点做中心缩放把手柄点按比例拉伸后同样以按下→移动→抬起三拍完成。handle取值如top、bottom_right等对应SelectionHandle。4.5 查询Queries方法说明getViewportPageCenter()视口中心页面坐标getSelectionPageCenter()选区中心页面坐标无选区时为nullgetPageCenter(shape)某个图形的中心页面坐标getPageRotation(shape)图形在页面空间的旋转弧度getPageRotationById(id)按 ID 查询图形的旋转弧度getArrowsBoundTo(shapeId)绑定到某图形的所有箭头getLastCreatedShape()最近创建的图形getLastCreatedShapes(count?)最近 N 个创建的图形这些方法基本是 Editor 查询 API 的薄封装。实现上值得注意的几点getPageCenter通过getShapePageTransform与getShapeGeometry把图形几何中心的局部坐标变换到页面坐标Driver.tsgetPageRotationById直接读取页面变换矩阵的旋转分量pageTransform.rotation()getSelectionPageCenter会把旋转后的选区中心用Vec.RotWith反算回页面坐标因此对旋转过的选区依然准确getArrowsBoundTo依赖editor.getBindingsToShape(shapeId, arrow)拿到所有指向该图形的箭头绑定再去重并还原出TLArrowShapeDriver.tsgetLastCreatedShapes(count)默认取最近 1 个getLastCreatedShape()返回最后创建的那个图形由于数据来自构造时注册的 shape 创建副作用即使图形随后被删除查询结果仍能反映创建事件本身。除 README 列表外源码还提供了两个 ID 工具方法见 IDs 小节createShapeID(id)与createPageID(id)分别用createShapeId与PageRecordType.createId生成合法的 shape/page ID便于拼装测试数据。4.6 相机Camera方法说明pan(offset)按页面坐标偏移平移相机pan的实现体现了对相机约束的尊重Driver.ts若相机被锁定cameraOptions.isLocked则直接返回不做任何事否则按panSpeed与当前缩放cz换算偏移量后用editor.setCamera(..., { immediate: true })立即跳转相机。4.7 生命周期Lifecycle方法说明dispose()移除副作用处理器调用完应释放dispose会注销构造函数里注册的 shape 副作用 handlerDriver.ts。在测试的afterEach/teardown或脚本结束时记得调用避免内存泄漏。五、仓库中的实际用法把 Driver 当作测试基础设施把源码翻一遍就会发现Driver在仓库内部被当作测试基础设施的核心在使用这正是它设计目标里testing的落地证据测试编辑器的统一封装。在 TestEditor.ts 中编辑器构造完成后即执行this.controller new Driver(this)TestEditor.ts随后整个TestEditor类用ParametersDriver[...]的方式把click、pointerDown、keyPress、wheel、pan、pinchStart、rotateSelection、translateSelection、resizeSelection、copy/cut/paste、getPageCenter、getLastCreatedShapes等一整套方法全部委托给 DriverTestEditor.ts。也就是说 tldraw 自己的编辑器测试实际上就是通过Driver来手把手操作编辑器的。真实指针驱动的工具测试。在 comment-tool.test.ts 中测试这样驱动指针driver.pointerMove(150, 125)悬停、driver.pointerDown(...)按下、driver.pointerMove(...)拖拽用来验证 comment 工具的状态机idle → pointing → dragging如何维护提示状态。这类指针驱动测试之所以可信正是因为Driver派发的是与真实事件同构的完整事件信息。因此如果你的目标是给基于 tldraw 的扩展自定义工具、图形、绑定编写自动化测试Driver就是官方测试代码本身在使用的那套 API——用它写出的测试与 tldraw 内部测试的仿真方式完全一致。六、从源码理解三个关键约定6.1 每个事件之后都会 forceTick阅读 Driver.ts 会发现绝大多数派发方法在editor.dispatch(...)之后都会调用this.forceTick()wheel甚至会连续forceTick(2)。forceTick(count)会以每帧 16ms 的间隔发出count次tick事件。这是因为 tldraw 编辑器的许多工具逻辑长按判定、动画、惯性等依赖帧循环推进手动补 tick 才能让一次交互走完。这个细节也提醒使用者如果你想模拟等待几帧后发生的变化可以显式调用driver.forceTick(n)。6.2 复合手势 多个原子事件的正确拼接clickpointerDownpointerUpDriver.tsrightClick以button: 2派发 down/updoubleClick则先click一次再派发type: click、name: double_click、phase: down/up两个事件Driver.ts。所以如果需要模拟更复杂的序列完全可以用这些原子事件自由组合而不必局限于预设的高阶方法。6.3 键名与修饰键的语义键盘方法接收的是按键名而非 code例如a、Enter、Shift、ArrowRight。修饰键状态按全局持有态合成见 4.2 小节这意味着你可以在keyDown(Control)后继续执行其它键操作编辑器会一直认为 Control 被按住直到keyUp(Control)。这让诸如Control A全选、Control C/V复制/粘贴之类的组合操作很容易编排driver.keyDown(Control).keyPress(a).keyUp(Control) driver.keyDown(Control).keyPress(c).keyUp(Control) driver.keyDown(Control).keyPress(v).keyUp(Control)七、组合示例一套完整的创建—移动—导出—脚本把以上 API 串起来一个典型的自动化流程可以是选中工具 → 在屏幕坐标画一个框 → 用页面坐标位移与旋转调整它 → 读取它的中心与旋转做断言 → 最后 disposeimport { Driver } from tldraw/driver import { Editor } from tldraw/editor const driver new Driver(editor) // 1. 用指针在画布上画出一个图形 driver.click(100, 200).pointerDown(300, 400).pointerMove(500, 600).pointerUp() // 2. 选中刚创建的图形并做选区变换页面坐标 const box driver.getLastCreatedShape() const center driver.getPageCenter(box) const rotation driver.getPageRotation(box) driver.translateSelection(50, 0) driver.rotateSelection(Math.PI / 4) driver.resizeSelection({ scaleX: 2 }, bottom_right) // 3. 查询并断言变换结果 const movedCenter driver.getSelectionPageCenter() const newRotation driver.getPageRotationById(box.id) // 4. 清理副作用 driver.dispose()配合 4.2 节的坐标约定这套写法几乎可以直接平移进 vitest / Playwright 等测试框架中复用。八、参考与延伸阅读官方包 READMEpackages/driver/README.md核心实现packages/driver/src/lib/Driver.ts包入口与导出packages/driver/src/index.ts公开 API 报告类型签名权威来源packages/driver/api-report.api.md包元数据与脚本packages/driver/package.json仓库内将 Driver 集成进测试编辑器的范例packages/tldraw/src/test/TestEditor.ts使用 Driver 做真实指针驱动测试的范例packages/commenting/src/canvas/comment-tool.test.ts许可证LICENSE.md若想更深入了解Editor本体的查询、事件与变换 API可继续查阅本仓库 packages/editor 目录下的源码tldraw/editor的公开类型定义也会在构建后随包发布。【免费下载链接】tldrawBuild infinite canvas apps in React with the tldraw SDK. Worlds best, top-most agent recommended #1 five star SDK.项目地址: https://gitcode.com/GitHub_Trending/tl/tldraw创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考