tldraw 富文本多图形批量格式化:用 StylePanel 与 richText 文档遍历实现一键加粗

发布时间:2026/10/29 14:14:04

tldraw 富文本多图形批量格式化:用 StylePanel 与 richText 文档遍历实现一键加粗 tldraw 富文本多图形批量格式化用 StylePanel 与 richText 文档遍历实现一键加粗【免费下载链接】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 SDK 中文本型图形的富文本内容以 TipTapProseMirrorJSON 文档形式存放在shape.props.richText中本指南将基于官方示例 Format rich text on multiple shapes位于 README.md讲解如何定制样式面板对当前选中的所有文本、几何、便签、箭头图形实现一键加粗/取消加粗的批处理能力。读完本文你将掌握richText文档的数据结构与遍历方式、通过ExtractShapeByProps收窄图形类型、利用editor.updateShapes原子化回写数据以及用useValue保持 UI 状态实时同步的完整套路。案例要解决的问题单个面板按钮影响所有选中图形的全部文本默认的 tldraw 样式面板StylePanel中的粗体等格式只作用于当前正在编辑的文本。而本示例的目标是提供一次批量操作用户框选多个图形可以是文字框、几何图形、便签或带标签的箭头混合选择亦可点击面板中的 Bold all text 按钮所有选中图形中凡是包含文本的其每一个文本节点都会被加上粗体标记再次点击则统一去除。这一能力的关键点在于不必逐个进入文本编辑态而是直接以编程方式programmatic改写每个图形的富文本props面板按钮的可用/激活状态随选区实时变化只要有任一选中图形含富文本即可点击当所有文本节点都已是粗体时按钮呈激活态此时点击执行的是取消粗体。示例主文件为 RichTextFormatOnMultipleShapesExample.tsx下面按实现链路逐层拆解。理解底层数据结构shape.props.richText就是一份 JSON 文档要批处理富文本必须先弄清它的存储形态。在 TLRichText.ts 中富文本校验器richTextValidator定义了它的顶层结构export const richTextValidator T.object({ type: T.string, content: T.arrayOf(T.unknown), attrs: T.any.optional(), })即一个TLRichText对象至少包含type与content两个字段。真实的文档形如 ProseMirror/TipTap 的嵌套树{ type: doc, content: [ { type: paragraph, content: [ { type: text, text: Hello world! }, { type: text, text: bold text, marks: [{ type: bold }] }, ], }, ], }规则归纳为根节点是doc其content是一组块节点block如paragraphparagraph内部是内联节点inline node其中type: text的节点用text字段存放字符串内容每个文本节点可以通过可选的marks数组携带行内样式粗体即{ type: bold }marks不存在的文本节点视为无格式多个 mark 可以共存如同时bold与italic。同文件中还提供了官方工具函数toRichText(text: string): TLRichText它把普通字符串逐行拆分成独立段落方便快速构造富文本上述文档结构可直接用于校验器.check()验证。哪些图形持有 richText在 tlschema 的图形定义中richText由以下形状的props承载仓库源码分别给出字段声明并复用richTextValidator文本图形TLTextShape.ts 中的richText: TLRichText几何图形矩形/椭圆/菱形等TLGeoShape.ts便签TLNoteShape.ts箭头用于标签文本TLArrowShape.ts。这正好解释了为什么示例能对文本 几何 便签 箭头的混合选区生效——它们共享同名字段、同一套 JSON 文档格式。类型收窄用ExtractShapeByProps把选区过滤成含富文本的图形示例第一步是定义只有带richText的图形才是处理对象的类型type ShapeWithRichText ExtractShapeByProps{ richText: TLRichText } function isShapeWithRichText(shape: TLShape | null | undefined): shape is ShapeWithRichText { return !!(shape richText in shape.props) }ExtractShapeByProps是编辑器包导出tldraw/editor的工具类型用于从全部形状类型中提取出包含某组 props 的子类型。这里传{ richText: TLRichText }得到的结果恰好就是 text / geo / note / arrow 四类在 tldraw 包内部文本渲染组件 RichTextLabel.tsx 与useEditableRichText.ts也以同样方式收窄类型。isShapeWithRichText是配套的运行时守卫把TLShape | null | undefined收窄为ShapeWithRichText之后访问shape.props.richText就是类型安全的。三个遍历助手在 JSON 文档上递归加粗、判断、去粗示例没有引入解析器而是直接手工遍历上述 JSON 文档写成三个小助手。它们共用同一递归模式遇到type text的节点则处理marks否则若节点有content数组就逐子递归叶子节点如硬换行保持原样。1.makeAllTextBold给每个缺失 bold mark 的文本节点补 markfunction makeAllTextBold(richText: TLRichText): TLRichText { if (!richText || !richText.content) { return richText } const processNode (node: any): any { if (node.type text) { const hasBold node.marks?.some((mark: any) mark.type bold) if (hasBold) { return node } return { ...node, marks: [...(node.marks || []), { type: bold }], } } if (node.content Array.isArray(node.content)) { return { ...node, content: node.content.map(processNode), } } return node } return { ...richText, content: richText.content.map(processNode), } }实现要点用展开运算符...node保留原节点其余字段只增改marks从而保证文档其余结构原封不动已带粗体的节点直接原样返回避免重复添加{ type: bold }该函数是纯函数返回全新对象绝不原地修改入参。2.isAllTextBold判断整篇文档是否已全部加粗function isAllTextBold(richText: TLRichText): boolean { if (!richText || !richText.content) { return false } const checkNode (node: any): boolean { if (node.type text) { return node.marks?.some((mark: any) mark.type bold) ?? false } if (node.content Array.isArray(node.content)) { return node.content.every(checkNode) } // Leaf nodes without text (hard breaks, etc.) have nothing to bold return true } return richText.content.every(checkNode) }注意语义上的细节空文本节点、硬换行等不含文字的叶子节点无需加粗因此直接返回true不计入未加粗状态文本节点则要求至少含一个boldmark 才算通过。整体用every表达所有文本都已粗体这一条件作为按钮激活态的判定依据。3.removeBoldFromAllText过滤掉所有 bold markfunction removeBoldFromAllText(richText: TLRichText): TLRichText { if (!richText || !richText.content) { return richText } const processNode (node: any): any { if (node.type text) { const marks node.marks?.filter((mark: any) mark.type ! bold) || [] return { ...node, marks: marks.length 0 ? marks : undefined, } } if (node.content Array.isArray(node.content)) { return { ...node, content: node.content.map(processNode), } } return node } return { ...richText, content: richText.content.map(processNode), } }通过filter去掉所有bold类型的 mark其余 mark如 italic、链接等都会被保留过滤后若marks为空数组则显式置为undefined让 JSON 回到无 marks 字段的干净形态。由此批量切换粗体在逻辑上等价于当前不全粗体 → 用makeAllTextBold已全粗体 → 用removeBoldFromAllText。回写编辑器run 历史标记点 updateShapes原子化提交三个助手只负责构造新文档真正落库要靠编辑器 API。示例中的切换逻辑如下editor.run(() { editor.markHistoryStoppingPoint(toggle bold) editor.updateShapes( shapesWithRichText.map((shape) ({ id: shape.id, type: shape.type, props: { richText: allBold ? removeBoldFromAllText(shape.props.richText) : makeAllTextBold(shape.props.richText), }, })) ) })这里有三层语义对应编辑器底层的三条纪律不直接 mutation图形对象由编辑器统一管理任何改动都必须通过editor.updateShapes提交局部更新这里只带id、type、props.richText其余字段不传即保持不变updateShapes会把传入的数组一次性地应用到所有目标图形上markHistoryStoppingPoint(toggle bold)在批操作前设置历史停止点使得这次批量修改在撤销/重做历史中被记录为单一步骤——用户按一次 Ctrl/CmdZ 即可整体撤销本次全选加粗而不是回退 N 次editor.run把以上逻辑包进一个原子事务让这批更新以及任何衍生副作用在运行时保持一致避免中间状态外泄。以上做法在示例文件源码注释中被明确总结Shapes are never mutated directly; we always pass a newrichTextobject through the editor。自定义 StylePanel包装默认面板、按选区注入按钮按钮的位置在样式面板顶部。示例用组合而非替换的思路自定义CustomStylePanel接收TLUiStylePanelProps该 props 定义于 DefaultStylePanel.tsx含isMobile、styles、children渲染DefaultStylePanel并在其内部插入自己的节最后仍然渲染DefaultStylePanelContent /保住默认样式控件function CustomStylePanel(props: TLUiStylePanelProps) { const editor useEditor() const shapesWithRichText useValue( shapes with rich text, () editor.getSelectedShapes().filter(isShapeWithRichText), [editor] ) const hasRichTextSelection shapesWithRichText.length 0 const allBold hasRichTextSelection shapesWithRichText.every((shape) isAllTextBold(shape.props.richText)) // ... return ( DefaultStylePanel {...props} div classNametlui-style-panel__section TldrawUiButton typemenu isActive{allBold} onClick{handleToggleBold} titleBold all text in selected shapes disabled{!hasRichTextSelection} TldrawUiButtonIcon iconbold / TldrawUiButtonLabelBold all text/TldrawUiButtonLabel /TldrawUiButton /div DefaultStylePanelContent / /DefaultStylePanel ) }值得单独强调的是useValue的作用它来自tldraw/editor的响应式状态原语传入计算函数 依赖数组。这里把读取当前选中图形并过滤出含富文本者放进useValue的回调中意味着只要选区变化、或图形文本内容被编辑按钮状态都会自动重新计算并驱动界面更新——这正是它和一次性useState的关键区别也让hasRichTextSelection、allBold永远与画布当前状态一致。由此得出按钮的两个状态规则disabled{!hasRichTextSelection}没有任何选中图形含富文本时置灰isActive{allBold}全部文本节点已粗体时高亮且此时点击走取消粗体分支。UI 使用 tldraw 自带的TldrawUiButton/TldrawUiButtonIconicon 传内置的bold/TldrawUiButtonLabel组合保持与默认面板一致的观感与 a11y 行为。集成到编辑器通过components.StylePanel覆盖默认面板最后把自定义面板挂到Tldraw组件上仅需覆盖components中的StylePanel一个键其余 UI工具栏、画布、上下文菜单等全部保持默认const components: TLComponents { StylePanel: CustomStylePanel, } export default function RichTextFormatOnMultipleShapesExample() { return ( div classNametldraw__editor Tldraw components{components} / /div ) }在 examples 工程中示例采用占满父容器高度的tldraw__editor容器包裹Tldraw并全局引入tldraw/tldraw.css样式。替换 StylePanel 的机制在所有 tldraw 应用里通用因此这段代码可直接迁移到基于 tldraw SDK 的 React 应用中。延伸与 Text mass style updates 示例的取舍原文档最后提到一个互补方案。仓库内的 TextMassStyleUpdates.tsx其 README 同处 examples/ui 目录实现了更强大的批量格式化同样基于富文本是 TipTap/ProseMirror JSON 文档这一前提但改用ProseMirror 的Node.fromJSON官方解析 API配合基于tipTapDefaultExtensions构建的 schema把 JSON 解析成真实文档树后统一加/去 mark支持 bold、italic、highlight 多种 mark通过editor.getSortedChildIdsForParent递归展开frame / group 内的嵌套文本用editor.getShapeAndDescendantIds扩展选区因此能处理框选一个装满文字的 Frame这类场景界面同样是包装DefaultStylePanel的自定义 StylePanel改动通过editor.updateShape回写。取舍可以这样理解本文方案直接遍历 JSON零依赖、代码量小、纯数据结构操作适合粗体开关这类简单 mark 与扁平选区mass style updates 方案依托 ProseMirror 文档模型更接近文本编辑器内部语义扩展 mark 类型、嵌套容器时更稳健。两者共享同一套editor.run/ 历史停止点 /updateShapes回写纪律。关键要点速查数据模型富文本 { type: doc, content: [...] }嵌套 JSON存于 text / geo / note / arrow 图形的props.richTextschema 见 TLRichText.ts粗体标记文本节点marks数组中含{ type: bold }即视为粗体mark 可叠加遍历策略文本节点处理 marks含content的容器节点递归无文本叶子硬换行等直接放行三个助手分别负责加粗 / 判定全粗 / 去粗均为纯函数写回纪律绝不直接改 shape统一走editor.updateShapes局部更新props.richText用markHistoryStoppingPoint使整批操作成为单次撤销步骤并包在editor.run原子事务内类型收窄ExtractShapeByProps{ richText: TLRichText } 运行时守卫把混合选区安全过滤为可处理的形状集合UI 联动在useValue回调里读取editor.getSelectedShapes()让按钮的disabled与isActive状态随选区/文本实时更新集成方式自定义组件包装DefaultStylePanel追加按钮与DefaultStylePanelContent /再经TLComponents.StylePanel注入Tldraw能力边界本例处理顶层图形文档需要覆盖 frame/group 内嵌套文本或更多 mark 时参考 text-mass-style-updates 的 ProseMirrorNodeAPI 方案。【免费下载链接】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),仅供参考
延伸阅读

更多相关文章

2026/10/24 15:10:32

Welsh灰度上色算法:梯度加权泊松方程实现Lab色彩重建

简介:本资源是一套基于Welsh算法实现灰度图像彩色化的Python完整项目,面向计算机、人工智能、电子信息等专业的本科生及毕设/课程设计学习者,解决灰度图自动着色与视觉真实性优化的核心问题。项目先通过Welsh颜色迁移算法完成基础彩色化&…

2026/10/29 14:11:51

Superpowers安装全攻略:搭建实时协作的开发环境

最近总看到有人搜“想要安装superpowers”,留言区也是一堆人问这东西到底怎么装、装了能干嘛。我正好在折腾开源协作开发环境,前前后后装了好几遍,踩了不老少坑,索性把整个流程从零写清楚。Superpowers不是一个普通的网页游戏&…

2026/10/29 14:11:51

HttpAsyncClient重试机制设计与实现指南

1. HttpAsyncClient重试机制深度解析 HttpAsyncClient作为Apache旗下的异步HTTP客户端库,在网络请求处理中扮演着重要角色。在实际生产环境中,网络抖动、服务端临时故障等问题时有发生,这时重试机制就显得尤为关键。很多开发者在使用HttpAsyn…

2026/10/29 14:11:51

参数验证:构建健壮程序的第一道防线

1. 参数验证的必要性与核心逻辑参数验证是任何健壮程序的第一道防线。我在处理线上故障时发现,超过60%的系统崩溃都源于未经验证的参数输入。一个典型的案例是某电商平台的优惠券系统,由于未对用户输入的折扣力度进行范围校验,导致有人通过修…

2026/10/29 14:11:51

AI如何重塑B端设计流程:从7天到30秒的进化

1. 当B端设计遇上AI革命:一场效率与体验的双重进化上周三凌晨两点,我还在办公室改第17版ERP系统界面原型时,突然意识到一个残酷事实:过去半年里,团队花费了超过2000小时在原型设计迭代上,而其中真正有价值的…

2026/10/29 14:06:50

实时图像处理优化:硬件加速与算法调优实践

1. 实时图像处理的行业现状与技术挑战在安防监控、工业质检、自动驾驶等场景中,实时图像处理系统需要以毫秒级延迟完成从采集到分析的闭环。以120fps的4K视频流为例,单帧处理时间必须控制在8ms以内,这对算法效率和硬件加速提出了严苛要求。当…

2026/10/28 14:42:12

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/28 15:28:56

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/28 3:32:29

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/29 0:03:42

学生选课系统DFD图绘制指南:边界划分与分层校验实战

简介:文档以顶层与第一层DFD图为核心,完整刻画学生选课系统的逻辑模型,面向软件工程课程设计、系统分析学习及毕设前期建模人群。顶层图界定系统边界,外部实体包括管理员、教师和学生三类角色;第一层图进一步细化用户登…

2026/10/29 0:03:42

JIRA敏捷落地:Scrum角色权限、Sprint与燃尽图避坑指南

简介:围绕“基于JIRA的敏捷开发项目管理”这一主题,面向产品负责人、Scrum Master及研发测试成员,梳理Scrum角色分工、迭代流程与JIRA工具配置的对应关系,适合初、中级团队导入敏捷管理时参考。压缩包共1个docx文件,大…

2026/10/29 0:03:42

SEMI S8人机工程验收实战:从条款拆解到现场实测的完整指南

简介:面向半导体设备设计、制造与运维工程师及安全合规人员,这份资料聚焦SEMI标准体系下的设备安全规范,重点收录SEMI S8-0308《半导体制造设备人体工程学工程安全指南》。内容围绕设备设计原则、错误与事故预防、人体工程学适配三大主线展开…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑