如何用 tldraw 内置性能 API 定位画布卡顿瓶颈

发布时间:2026/9/10 10:56:48
如何用 tldraw 内置性能 API 定位画布卡顿瓶颈 如何用 tldraw 内置性能 API 定位画布卡顿瓶颈【免费下载链接】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当 tldraw 应用里的画布出现拖拽、平移或缩放卡顿尤其是画布上形状数量较多时靠肉眼判断很难说清瓶颈出在渲染、剔除culling还是自定义形状组件。tldraw SDK 内置了性能测量能力editor.performance事件订阅接口和PerformanceApiAdapter时间线适配器配合getCulledShapes()、getCurrentPageShapeIds()等计数 API可以把哪次交互掉帧、当时有多少形状参与渲染这类数据直接拿到手。本文按先取基线数据 → 订阅性能事件 → 接入 Chrome DevTools 时间线 → 对照检查项定位的顺序给出可照做的排查路径。适用前提你已经有一个基于tldraw包渲染Tldraw /组件的 React 应用并且能拿到Editor实例例如通过Tldraw的onMount回调。排查性能问题时建议使用生产构建测试文档指出开发模式存在生产构建没有的额外开销数据不能直接代表线上表现。先取基线数据页面形状数与被剔除形状数排查前先弄清当前页面上有多少形状在扛渲染压力。文档给出的两个入口editor.getCurrentPageShapeIds().size当前页面的形状总数editor.getCulledShapes()被视口剔除、不参与渲染的形状集合。tldraw 对视口外的形状做自动剔除把它们设为display: none所以一个有 10,000 个形状的画布可能只实际渲染其中 50 个。剔除集合是响应式的并且内容不变时返回同一个Set实例在useValue等位置读取开销很小。文档中给出的示例写法import { Tldraw } from tldraw import tldraw/tldraw.css export default function CullingExample() { return ( div style{{ position: fixed, inset: 0 }} Tldraw onMount{(editor) { // 视口外的形状未做选中过滤前 const notVisible editor.getNotVisibleShapes() // 真正不渲染的形状排除了选中/正在编辑的形状 const culled editor.getCulledShapes() console.log(Not visible:, notVisible.size) console.log(Actually culled:, culled.size) }} / /div ) }两个 API 的区别getNotVisibleShapes()返回页边界与视口不相交、且形状类型允许剔除的形状getCulledShapes()在此基础上再排除选中形状和正在编辑的形状让用户始终能看到自己正在操作的形状。如果culled.size明显小于预期说明有形状绕过了剔除常见原因是自定义ShapeUtil重写了canCull返回false——这正是文档列出的卡顿排查项之一。订阅 editor.performance 事件拿到逐次交互的帧率数据editor.performancePerformanceManager暴露了聚合的帧时间统计数据来自真实交互而不是预设基准。内部 hook 是惰性的没有任何监听器时不挂接 frame/shape 事件也就没有额外开销可以安全地常驻在应用里。文档给出的核心用法const unsub editor.performance.on(interaction-end, (event) { console.log(${event.name}: ${event.fps.toFixed(1)} fps, p95${event.p95FrameTime.toFixed(1)}ms) }) // 不再需要时unsub()on返回一个取消订阅函数配合onMount之外的一次性探测使用用完调用unsub()即可。主要事件及用途完整定义见 TLPerfEventMap 类型定义interaction-end交互状态退出时触发内置交互如select.translating、draw.drawing已被跟踪。事件里带fps、p95FrameTime、shapeCount当前页形状总数、selectedShapeTypes、zoomLevel等字段是定位哪类操作掉帧的主力事件。camera-end平移/缩放在防抖结束后触发额外带visibleShapeCount、culledShapeCount、视口宽高。用它可以直接对比平移时实际参与渲染的形状数与页面总数判断剔除是否生效。shapes-created/shapes-updated/shapes-deleted携带按类型统计的数量用于判断批量操作是否触发了异常多的更新。frame只要有frame监听器每个动画帧都会触发携带elapsed、shapeCount、culledShapeCount、visibleShapeCount适合做应用内实时面板。interaction-start、camera-start对应的开始事件只有name/path/type和时间戳一般配合 end 事件使用。实现细节上interaction-end事件的统计值avg/median/p95/p99 帧时间、fps由 PerformanceManager 在交互窗口内逐帧记录后聚合得出事件中的frameTimes是原始帧时长数组、longAnimationFrames是长动画帧条目——类型注释明确提示这两项在上报分析系统时应剔除条目较大且包含脚本 URL。如果浏览器支持 Long Animation Frames APIlongAnimationFrames字段注释标明为 Chromium 123end 事件还会附带longAnimationFrames其中包含主线程阻塞时长blockingDuration和具体脚本归属sourceURL、invoker、duration可以直接指认是哪段代码把主线程卡住了。接入 Chrome DevTools用 PerformanceApiAdapter 把事件画进时间线在 DevTools 里做 profile 时可以用PerformanceApiAdapter把同一套事件转成原生的performance.mark()/performance.measure()调用使其出现在 Performance 时间线上import { PerformanceApiAdapter } from tldraw const adapter new PerformanceApiAdapter(editor.performance) // 不再需要时adapter.dispose()根据 PerformanceApiAdapter 源码它订阅了 interaction 和 camera 的 start/end 事件产生的 mark/measure 名称形如tldraw:interaction:name:start、tldraw:interaction:name、tldraw:camera:panning等mark 的detail中带有path、fps、frameCount、shapeCount。在 DevTools 的 Performance 面板录制一次拖拽或缩放就能在时间线上看到对应的 measure 区间及其携带的帧率数据。注意performance.mark的detail参数在 Safari/Firefox 可能抛错适配器内部已做了 try/catch 降级处理。自定义工具想进入交互统计需要在 StateNode 类上设置static trackPerformance true该静态属性默认值为false见 StateNode进入该状态时 PerformanceManager 开始一个跟踪窗口退出时发出interaction-start/interaction-end事件里的name/path就是该状态路径。内置交互已默认跟踪只有自定义状态需要这一步。拿到数据后对照哪些检查项文档在 Measuring performance 一节给出的定位思路是先用数字页面形状数、被剔除形状数建立基线然后用 React DevTools 和 Chrome 的 Performance 面板找慢组件并且一定要用生产构建验证。如果形状多时性能下降按顺序检查以下四项它们对应文档明确列出的常见瓶颈不必要的剔除豁免查找重写了canCull返回false的形状类型。重写canCull的形状会在剔除推导中被单独订阅读取多个 props 的canCull会让推导更频繁地重跑而默认快路径形状不承担这个成本。文档建议只在确实需要时禁用剔除靠 DOM 测量决定尺寸的形状、光晕/阴影等超出自身边界的视觉效果、需要在视口外继续运行的动画。用错缩放 API查找形状组件里使用getZoomLevel()而不是getEfficientZoomLevel()的地方。后者在文档中定义为当页面形状数超过 500 个由debouncedZoomThreshold选项配置时相机移动期间返回稳定值、相机停止后才更新为真实缩放级别避免缩放过程中逐帧重算导致卡顿。组件函数里的昂贵计算形状组件渲染频繁避免在渲染函数体内直接计算改用useMemo并把与命中测试/边界相关的计算放进getGeometry()editor 会自动缓存几何结果。大量形状的持续动画动画形状属性会触发持续重渲染纯视觉特效用 CSS 动画、粒子类效果用 canvas并保持同时动画的形状数量少。SDK 的动画系统处理的是相机移动和偶发的形状过渡不为逐形状持续动画设计。camera-end事件里的visibleShapeCount/culledShapeCount可以帮助第 1 项判断如果视口很小但可见形状数仍然很高优先去查canCull的覆盖面。可调整的相关 editor 选项与渲染性能相关的选项默认值来自文档中的表格选项默认值作用debouncedZoomtrue相机移动期间使用稳定缩放值debouncedZoomThreshold500超过该形状数时启用防抖缩放maxShapesPerPage4000每页允许的最大形状数textShadowLod0.35缩放低于该阈值时关闭文字描边以降低成本在Tldraw上传入import { Tldraw } from tldraw function App() { return ( Tldraw options{{ debouncedZoomThreshold: 1000, // 更简单的文档可以调高阈值 maxShapesPerPage: 10000, // 确有需要时允许更多形状 }} / ) }文档示例中的两个值只是示例配置是否调整取决于你的形状数量和交互目标不要把示例数值当成推荐值。另外内置形状自带低缩放级别下的 LOD 简化便签去掉阴影、虚线/点线手绘改实线、图案填充改纯色等这些转换同样基于getEfficientZoomLevel()自定义形状可以用同样的技术在形状屏幕尺寸较小时渲染简化版本。限制与边界longAnimationFrames只在支持 Long Animation Frames API 的浏览器出现类型注释标注为 Chromium 123其他浏览器下事件中不会带该字段排查需要退回 fps/帧时间统计加 DevTools 手动 profile。内置交互已被跟踪自定义状态必须显式设置trackPerformance才会出现在interaction-end里。性能事件只在有监听器时才产生开销frame事件同理只在注册监听后每帧触发。上报遥测时按类型定义中的注释剔除frameTimes和longAnimationFrames两个字段。更多细节见文档中的 Performance 与 Culling 两篇前者还覆盖响应式信号、批量 store 更新、几何缓存与图像分辨率缩放LOD等 SDK 自动完成的优化以及自定义形状侧的取舍建议。【免费下载链接】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),仅供参考