toolgarden.xyz
EN
浏览器工具开发tldraw在线白板Next.js前端工程

怎么封装 tldraw 实现在线白板:从动态加载到本地持久化

基于 Next.js 和 React 封装 tldraw,完整处理客户端加载、静态资源、本地持久化、工具栏、页面滚动与全屏适配。

ToolGarden 推荐的工具优先在浏览器本地运行,文件和文本不必上传到服务器,适合更注重安全隐私的日常处理。

发布于 2026年7月22日约 12 分钟阅读作者 ToolGarden

tldraw 能快速给 React 应用增加无限画布,但把 <Tldraw /> 放进 Next.js 页面,只完成了不到百分之五。真正的产品封装从加载、资源、持久化、滚动归属、响应式控制、全屏和授权部署开始。

本文拆解一套真实的浏览器本地封装:白板数据保存在用户浏览器中,编辑器字体与图标由站点自行托管,支持中英文界面,宽屏使用纵向工具栏,并且在普通页面中优先让网页滚动,进入全屏后再把滚轮交回画布。

先划清 tldraw 与产品外壳的职责

把 tldraw 当作产品外壳里的编辑器运行时。页面布局、加载与重试、隐私提示、全屏、统计边界和导航属于应用;图形、选择、相机、撤销历史、页面和原生导出属于 tldraw。

边界明确后,就不会一边重复实现库已经做好的编辑能力,一边忽略画布周围真正影响用户体验的产品行为。

能力负责方
图形、工具、选择、多页面tldraw
路由、标题、帮助文案、异常状态应用外壳
文档持久化persistenceKey 或自定义 store
协作与身份认证业务后端和同步架构
静态资源与 CSP应用部署层

只在浏览器加载 tldraw

画布编辑器依赖浏览器 API,而且 JavaScript 体积不小。把封装组件标记为 client component,在该工具的 route layout 引入 tldraw/tldraw.css,等页面 hydration 后再动态 import 运行时。保存组件函数时要使用 setState(() => Component),否则 React 可能把组件函数当作状态更新器执行。

effect 内还要记录组件是否仍然挂载,避免慢速加载完成后更新已经卸载的页面。加载失败时可以递增加载次数,让用户点击按钮重新请求 chunk。

// Client component: load the editor only after hydration.
useEffect(() => {
  let active = true;

  import('tldraw').then(({ Tldraw, DefaultToolbar }) => {
    if (!active) return;
    setEditor(() => Tldraw);
    setToolbar(() => DefaultToolbar);
  });

  return () => { active = false; };
}, []);

return Editor ? (
  <Editor
    assetUrls={assetUrls}
    locale={locale === 'zh' ? 'zh-cn' : 'en'}
    persistenceKey="my-whiteboard"
    cameraOptions={{ wheelBehavior: fullscreen ? 'pan' : 'none' }}
  />
) : <LoadingState />;

把编辑器运行时资源全部本地化

白板可能在开发环境一切正常,上线后却因为严格 CSP、离线部署或外部资源不可用而丢失字体、翻译、图标和嵌入缩略图。通过 assetUrls 把运行时资源统一映射到同源的版本化目录。

资源映射应集中在一个工具模块里。升级 tldraw 时,对比新版资源清单与本地文件,把缺失资源变成构建期错误,而不是等生产工具栏出现空白才发现。

const base = '/tldraw-assets';

export const assetUrls = {
  icons: { 'align-left': `${base}/icons/align-left.svg` },
  fonts: { tldraw_sans: `${base}/fonts/IBMPlexSans-Medium.woff2` },
  translations: { 'zh-cn': `${base}/translations/zh-cn.json` },
  embedIcons: { youtube: `${base}/embed-icons/youtube.png` },
};
  • 复制图标雪碧图或实际需要的独立图标。
  • 包含文字工具会用到的全部字体粗细和斜体。
  • 准备语言切换器可能选择的 locale JSON。
  • 开放嵌入能力时,同时托管 embed icons。

用 persistenceKey 实现单用户本地白板

稳定的 persistenceKey 让编辑器在刷新后从浏览器本地恢复白板,很适合无需账号的单用户工具,画布内容也不必提交到应用 API。key 要带产品前缀,避免同一域名下的多个白板互相覆盖。

本地持久化不是云端备份。清除站点数据、切换浏览器配置或使用无痕模式,都可能让文档消失。如果产品承诺账号同步、多人协作、历史版本或跨设备访问,就应把它们设计成明确的后端能力,不能暗示 persistenceKey 已经做到。

  • 说明本地保存了什么,以及用户如何清除。
  • 不要在客户端可见的 key 里放用户密钥。
  • 提供导出能力,让用户可以自行备份。
  • 升级库导致数据结构变化时,准备版本标识或迁移策略。

解决画布滚轮与网页滚动冲突

无限画布天然希望独占滚轮做平移和缩放,但嵌在长页面中间的白板不应该截断网页纵向滚动。一种实用策略是:普通嵌入状态把 wheelBehavior 设为 none,把没有修饰键的滚轮位移转发给 window.scrollBy;进入全屏后再把滚轮恢复为画布 pan。

Ctrl 或 Command 加滚轮通常用于编辑器缩放,应保留给画布;零位移事件无需转发。除普通鼠标外还要测试触控板,因为横向位移和惯性滚动差异很大。

通过 components 定制工具栏

components prop 允许应用替换部分编辑器 UI,而不需要 fork tldraw。对于纵向空间充足的工作区,可以用 DefaultToolbar 包一层 vertical orientation,把常用工具放在左侧,同时释放顶部横向空间,再用少量 CSS 把它定位在画布框内。

优先组合默认组件,不要复制内部工具栏实现。默认组件会继承库里的快捷键、工具状态与可访问性行为,复制版本在升级后很容易漂移。

  • 按真实画布高度设置最小和最大可见工具数。
  • 保证触摸和键盘用户仍能访问工具说明。
  • 先使用编辑器提供的组件 API,再考虑覆盖内部 class。
  • CSS 覆盖尽量只处理定位和产品语义色。

实现有降级能力的全屏

原生 requestFullscreen 隔离效果最好,但浏览器、iframe 或 Permissions Policy 都可能拒绝。捕获异常后,可以把容器切换为 fixed inset-0 作为 CSS 全屏降级;降级状态下锁住 body 滚动,并监听 Escape 退出。

无论原生全屏还是 CSS 降级,布局稳定后都应触发一次 resize。画布编辑器会缓存视口尺寸,如果仍使用旧尺寸,工具栏可能被裁掉,相机中心也会偏移。

上线前处理异常、授权与升级

chunk 下载过程中显示真实 loading surface,import 失败后展示可理解的错误和重试按钮。根据所用版本确认 tldraw 的授权要求,并按官方部署规则配置客户端 license key。浏览器下发的 key 不是秘密,真正约束来自域名限制和许可证条款。

固定依赖版本,升级前回归本地持久化和导出,并在 Network 面板检查是否仍有意外外部资源。如果未来加入协作,应把身份认证、文档访问控制、在线状态、冲突解决与保存周期当作独立架构项目。

总结

生产级 tldraw 封装的重点是集成工程:只在客户端加载、确定性托管资源、明确本地持久化语义、设计滚轮归属、通过组件 API 定制,以及可靠处理视口变化。应用外壳做好这些边界,图形编辑能力则交给编辑器运行时。

常见问题

Q.tldraw 可以直接放在 Next.js Server Component 里吗?

路由外壳和 metadata 可以服务端渲染,但编辑器本身依赖浏览器 API,应放在 client component。hydration 后动态加载还能让较大的编辑器运行时离开服务端渲染路径。

Q.persistenceKey 能让白板跨设备同步吗?

不能。它提供的是当前域名、当前浏览器配置下的本地持久化。跨设备同步或多人协作需要带身份认证的存储与同步层。

Q.为什么要自己托管 tldraw 静态资源?

同源托管能让资源可用性、CSP、离线行为、缓存和版本匹配都由应用控制,也能避免外部资源故障导致编辑器 UI 不完整。

Q.为什么鼠标放在画布上后网页不能滚动?

编辑器会消费滚轮事件来移动相机。嵌入页面必须制定明确策略:普通状态禁用或拦截画布滚轮,让文档页面滚动;全屏时再恢复画布平移。

Q.tldraw 本地持久化适合保存重要文档吗?

它适合本地草稿,但不是受管理的备份。重要文档还应支持导出;如果产品承诺账号存储,则需要经过测试的后端、保存周期与恢复流程。