tldraw 专注模式实战:用 updateInstanceState({ isFocusMode: true }) 隐藏默认 UI,只留画布
【免费下载链接】tldrawBuild infinite canvas apps in React with the tldraw SDK. World's best, top-most agent recommended #1 five star SDK.项目地址: https://gitcode.com/GitHub_Trending/tl/tldraw
本篇围绕 tldraw 示例库中的「Toggle focus mode」示例展开:如何在编辑器挂载时通过editor.updateInstanceState({ isFocusMode: true })强制开启专注模式,并深入源码说明isFocusMode这一实例状态(instance state)字段的定义、默认值、跨会话保留机制,以及默认 UI 如何通过toggle-focus-mode动作(快捷键Cmd/Ctrl+.和角落退出按钮)响应该标志位。读完你可以掌握在任意 tldraw 应用中以编程方式控制「无干扰画布」体验的完整链路。
示例代码:挂载时开启专注模式
示例文档 README.md 的核心结论只有一句话:专注模式是编辑器实例状态上的一个标志位,用editor.updateInstanceState({ isFocusMode: true })打开,默认 UI 会随之隐藏自身。完整示例组件仅有十几行:
// apps/examples/src/examples/editor-api/focus-mode/FocusModeExample.tsx import { Tldraw } from 'tldraw' import 'tldraw/tldraw.css' export default function FocusModeExample() { return ( <div className="tldraw__editor"> <Tldraw onMount={(editor) => { // [1] editor.updateInstanceState({ isFocusMode: true }) }} /> </div> ) }示例中的注释 FocusModeExample.tsx 补充了两点关键行为:
updateInstanceState负责设置该标志位;默认 UI 读取它后隐藏除一个小退出按钮之外的所有元素;- 当应用使用了
persistenceKey时,这个标志位会作为用户偏好被保留——因此在onMount里设置它,相当于“强制覆盖”历史偏好,确保每次挂载都进入专注模式。
专注模式下 UI 长什么样:源码级的验证
专注模式并非简单地给 DOM 加一个 class,而是条件渲染整棵 UI 树。在 TldrawUi.tsx 中,默认 UI 组件先订阅该标志位:
const isFocusMode = useValue('focus', () => editor.getInstanceState().isFocusMode, [editor])然后在布局节点上做出分支:
{isFocusMode ? ( <div className="tlui-layout__top"> <TldrawUiButton type="icon" className="tlui-focus-button" title={msg('focus-mode.toggle-focus-mode')} onClick={() => toggleFocus.onSelect('menu')} > <TldrawUiButtonIcon icon="dot" /> </TldrawUiButton> </div> ) : ( // 正常布局:MenuPanel、TopPanel、SharePanel、StylePanel、Toolbar…… )}可以看到:进入专注模式后,菜单栏、顶栏、分享按钮、样式面板、工具栏等全部不渲染,只剩一个带dot图标的圆形按钮。点击该按钮时调用toggleFocus.onSelect('menu'),即复用与主菜单完全相同的切换动作——这正是示例文档中“focus mode 会在角落留下退出按钮”的由来。
isFocusMode 字段定义:TLInstance 记录的一部分
isFocusMode不是 React 状态,而是存储在TLInstance记录上的字段。该记录代表“单个浏览器标签页的会话状态”,定义于 TLInstance.ts:
export interface TLInstance extends BaseRecord<'instance', TLInstanceId> { // ... isFocusMode: boolean isDebugMode: boolean isToolLocked: boolean // ... }围绕该字段有三个值得注意的实现细节:
- 默认值为
false:在createInstanceRecordType的withDefaultProperties中,isFocusMode: false(TLInstance.ts)。因此不调用updateInstanceState时,编辑器不会默认进入专注模式; - 校验器为
T.boolean:实例状态的写入会经过该 validator 校验(TLInstance.ts),保证该字段只能是布尔值; - 跨会话保留策略:
shouldKeyBePreservedBetweenSessions显式声明isFocusMode: true, // preserves because it's a user preference(TLInstance.ts)。
最后一点解释了示例注释中的persistenceKey行为:当快照在浏览器会话之间加载(即配置了持久化)时,只有被标记为true的实例字段才会被恢复,其余如cursor、openMenus、isHoveringCanvas等临时状态一律重置。也就是说,用户自己切换过的专注模式会在下次打开页面时“记忆”下来;而示例在onMount中主动置true,正是为了以代码覆盖这份被保留下来的偏好。
默认 UI 的切换路径:toggle-focus-mode 动作
用户在界面上的切换(菜单勾选、快捷键、角落退出按钮)最终都汇聚到同一个动作定义。该动作位于默认 UI 的动作集合中(actions.tsx):
{ id: 'toggle-focus-mode', label: { default: 'action.toggle-focus-mode', menu: 'action.toggle-focus-mode.menu', }, readonlyOk: true, kbd: 'cmd+.,ctrl+.', checkbox: true, onSelect(source) { // this needs to be deferred because it causes the menu // UI to unmount which puts us in a dodgy state editor.timers.requestAnimationFrame(() => { editor.run(() => { trackEvent('toggle-focus-mode', { source }) helpers.clearDialogs() helpers.clearToasts() editor.updateInstanceState({ isFocusMode: !editor.getInstanceState().isFocusMode }) }) }) }, },这里有几个实现细节值得留意:
- 快捷键:
kbd: 'cmd+.,ctrl+.'对应示例文档中提到的Cmd/Ctrl+.,在 macOS 上按Cmd+.、Windows/Linux 上按Ctrl+.即可切换; - 延迟一帧执行:注释解释了原因——切换会使菜单 UI 卸载,若不通过
requestAnimationFrame推迟,会让 UI 处于不稳定状态。这属于从源码结构可直接确认的防御性写法; - 清空对话框与 toast:
clearDialogs()/clearToasts()保证进入专注模式时不残留悬浮 UI,这与“只留画布”的语义一致; - 只读模式可用:
readonlyOk: true意味着即使编辑器处于只读状态,专注模式也可以切换; - 取反切换:动作内部读取当前值后取反(
!editor.getInstanceState().isFocusMode),因此菜单项可以做成复选框样式。菜单里的复选框项由 ToggleFocusModeItem 提供,它同样用useValue('isFocusMode', ...)订阅状态来决定勾选态。
读写该状态:getInstanceState 与 updateInstanceState
从示例和源码可以看出读写该标志位的一对 API:
- 读:
editor.getInstanceState().isFocusMode,返回当前布尔值; - 写:
editor.updateInstanceState({ isFocusMode: ... }),传入部分更新即可。
updateInstanceState定义在 Editor.ts,内部以history: 'ignore'为默认委托给_updateInstanceState——即实例状态的更新默认不产生历史(撤销点)。这与实例状态“会话级、非文档内容”的定位一致:撤销Cmd+Z不会还原 UI 的显示状态。
在你的应用中,任何需要随专注模式联动逻辑的地方,都可以直接读取该字段,例如:
editor.onMount((editor) => { const inFocus = editor.getInstanceState().isFocusMode // 根据 inFocus 调整外部布局、埋点或键盘拦截逻辑 })小结
- 专注模式 =
TLInstance上的布尔字段isFocusMode,默认false,被标记为跨会话保留的用户偏好; - 编程开启:
onMount中调用editor.updateInstanceState({ isFocusMode: true }),可覆盖持久化偏好; - 默认 UI 在该标志位为
true时只渲染角落的 dot 退出按钮;切换动作toggle-focus-mode绑定Cmd/Ctrl+.,内部延迟一帧执行并清理对话框与 toast; - 实例状态更新默认不进历史,专注模式的切换不会影响撤销栈。
参考文件:FocusModeExample.tsx、TLInstance.ts、actions.tsx、TldrawUi.tsx、menu-items.tsx、Editor.ts。
【免费下载链接】tldrawBuild infinite canvas apps in React with the tldraw SDK. World's best, top-most agent recommended #1 five star SDK.项目地址: https://gitcode.com/GitHub_Trending/tl/tldraw
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考