

tldraw 在线绘图系统技术解析
一、系统概述
本项目是一个基于 **tldraw** 构建的在线绘图系统示例集合,旨在展示 tldraw 库的核心功能和扩展能力。系统采用 React 19 + Vite 8 技术栈,包含六个循序渐进的示例,从基础使用到高级自定义,全面覆盖了 tldraw 的主要应用场景。
1.1 技术栈
| 技术 | 版本 | 用途 |
|------|------|------|
| React | 19.2.7 | UI 框架 |
| React DOM | 19.2.7 | DOM 渲染 |
| tldraw | 5.1.1 | 绘图引擎核心 |
| Vite | 8.1.0 | 构建工具 |
| @vitejs/plugin-react | 6.0.3 | React 插件 |
1.2 项目结构
```
tldraw/
├── src/
│ ├── main.jsx # 应用入口
│ ├── App.jsx # 主应用组件(示例切换器)
│ ├── index.css # 全局样式
│ └── examples/ # 示例组件目录
│ ├── BasicExample.jsx # 基础示例
│ ├── WithInitialDataExample.jsx # 初始数据示例
│ ├── CustomUIExample.jsx # 自定义UI示例
│ ├── SaveLoadExample.jsx # 保存加载示例
│ ├── EventExample.jsx # 事件监听示例
│ └── CustomShapeExample.jsx # 自定义形状示例
├── index.html # HTML 模板
├── vite.config.js # Vite 配置
└── package.json # 依赖配置
```
二、核心架构
2.1 应用入口
main.jsx(file:///C:/Users/sd/Desktop/tldraw/src/main.jsx) 是应用的入口文件,负责初始化 React 应用:
```javascript
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
```
2.2 主应用组件
App.jsx(file:///C:/Users/sd/Desktop/tldraw/src/App.jsx) 作为应用的主组件,承担以下职责:
-
**示例路由管理**:通过 `useState` 管理当前选中的示例
-
**导航栏渲染**:动态生成示例切换按钮
-
**示例展示**:根据当前选择渲染对应的示例组件
```javascript
const examples = [
{ id: 'basic', name: '基础示例', component: BasicExample, desc: '最简单的 tldraw 使用方式' },
{ id: 'initial', name: '初始数据', component: WithInitialDataExample, desc: '带预设形状的画板' },
{ id: 'custom-ui', name: '自定义UI', component: CustomUIExample, desc: '添加自定义工具栏' },
{ id: 'save-load', name: '保存/加载', component: SaveLoadExample, desc: '保存和加载画板数据' },
{ id: 'events', name: '事件监听', component: EventExample, desc: '监听编辑器事件' },
{ id: 'custom-shape', name: '自定义形状', component: CustomShapeExample, desc: '创建自定义便签卡片' },
]
```
三、示例详解
3.1 基础示例(BasicExample)
BasicExample.jsx(file:///C:/Users/sd/Desktop/tldraw/src/examples/BasicExample.jsx) 展示了 tldraw 的最小化使用方式:
```javascript
import { Tldraw } from 'tldraw'
import 'tldraw/tldraw.css'
export default function BasicExample() {
return (
<div style={{ width: '100%', height: '100vh' }}>
<Tldraw />
</div>
)
}
```
**核心要点:**
-
只需引入 `Tldraw` 组件和样式文件
-
默认提供完整的绘图工具栏和画布功能
-
支持矩形、椭圆、文本、箭头等基础形状
3.2 初始数据示例(WithInitialDataExample)
WithInitialDataExample.jsx(file:///C:/Users/sd/Desktop/tldraw/src/examples/WithInitialDataExample.jsx) 演示如何在初始化时加载预设形状:
```javascript
const initialData = useCallback(() => {
return {
shapes: [
{
id: createShapeId('rect1'),
type: 'geo',
x: 100,
y: 100,
props: {
geo: 'rectangle',
w: 200,
h: 150,
color: 'blue',
fill: 'solid',
},
},
// ... 更多形状
],
}
}, \[\])
<Tldraw snapshot={initialData()} />
```
**核心要点:**
-
使用 `createShapeId` 生成唯一形状 ID
-
通过 `snapshot` 属性传入初始数据
-
支持多种形状类型:`geo`(几何图形)、`text`(文本)、`arrow`(箭头)等
3.3 自定义UI示例(CustomUIExample)
CustomUIExample.jsx(file:///C:/Users/sd/Desktop/tldraw/src/examples/CustomUIExample.jsx) 展示如何创建自定义工具栏:
```javascript
const CustomToolbar = track(() => {
const editor = useEditor()
const addRectangle = () => {
editor.createShapes([
{
type: 'geo',
x: Math.random() * 500,
y: Math.random() * 500,
props: {
geo: 'rectangle',
w: 100,
h: 100,
color: 'blue',
},
},
])
}
return (
<div style={{ position: 'absolute', top: 10, left: 10, zIndex: 1000 }}>
<button onClick={addRectangle}>添加矩形</button>
</div>
)
})
```
**核心要点:**
-
使用 `track` 函数包装组件使其响应编辑器状态变化
-
通过 `useEditor` Hook 获取编辑器实例
-
调用 `editor.createShapes()` 方法创建新形状
-
使用 `editor.getSelectedShapeIds()` 获取选中的形状
3.4 保存加载示例(SaveLoadExample)
SaveLoadExample.jsx(file:///C:/Users/sd/Desktop/tldraw/src/examples/SaveLoadExample.jsx) 演示画板数据的持久化:
```javascript
const handleSave = useCallback(() => {
const snapshot = editor.store.getSnapshot()
const json = JSON.stringify(snapshot, null, 2)
localStorage.setItem('tldraw-snapshot', json)
const blob = new Blob(json, { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'tldraw-snapshot.json'
a.click()
URL.revokeObjectURL(url)
}, editor)
const handleLoad = useCallback(() => {
const json = localStorage.getItem('tldraw-snapshot')
if (json) {
const snapshot = JSON.parse(json)
editor.store.loadSnapshot(snapshot)
}
}, editor)
```
**核心要点:**
-
使用 `editor.store.getSnapshot()` 获取当前状态快照
-
通过 `localStorage` 实现本地存储
-
使用 `Blob` API 实现文件下载
-
调用 `editor.store.loadSnapshot()` 恢复状态
3.5 事件监听示例(EventExample)
EventExample.jsx(file:///C:/Users/sd/Desktop/tldraw/src/examples/EventExample.jsx) 展示编辑器事件系统:
```javascript
<Tldraw
onMount={(editor) => {
editor.on('create-shape', (shape) => {
console.log('创建形状:', shape.type, shape.id)
})
editor.on('delete-shape', (shape) => {
console.log('删除形状:', shape.type, shape.id)
})
editor.on('select', (shapes) => {
console.log('选择变化:', shapes.length, '个形状')
})
editor.on('change-tool', (tool) => {
console.log('切换工具:', tool)
})
}}
>
<EventLogger />
</Tldraw>
```
**核心要点:**
-
通过 `onMount` 回调获取编辑器实例
-
使用 `editor.on()` 监听各种事件
-
常用事件:`create-shape`、`delete-shape`、`select`、`change-tool`
-
自定义组件作为 children 传入时可访问 `useEditor()`
3.6 自定义形状示例(CustomShapeExample)
CustomShapeExample.jsx(file:///C:/Users/sd/Desktop/tldraw/src/examples/CustomShapeExample.jsx) 展示如何创建自定义形状:
```javascript
const cardShapeProps = {
w: T.number,
h: T.number,
title: T.string,
content: T.string,
color: T.string,
}
class MyCardShapeUtil extends BaseBoxShapeUtil {
static type = 'my-card'
static props = cardShapeProps
getDefaultProps() {
return {
w: 200,
h: 150,
title: '标题',
content: '点击编辑内容...',
color: '#FFE066',
}
}
component(shape) {
return (
<HTMLContainer
style={{ width: shape.props.w, height: shape.props.h }}
>
<div style={{ background: shape.props.color }}>
<div>{shape.props.title}</div>
<div>{shape.props.content}</div>
</div>
</HTMLContainer>
)
}
getIndicatorPath(shape) {
const { w, h } = shape.props
const rx = 8
const path = new Path2D()
path.moveTo(rx, 0)
path.lineTo(w - rx, 0)
// ... 构建圆角矩形路径
return path
}
}
```
**核心要点:**
-
定义形状属性验证规则(`cardShapeProps`)
-
继承 `BaseBoxShapeUtil` 创建形状工具类
-
实现 `component()` 方法定义形状的 React 渲染
-
实现 `getIndicatorPath()` 方法返回选中时的指示器路径
-
通过 `shapeUtils` 属性注册自定义形状
四、tldraw 核心概念
4.1 Editor(编辑器)
Editor 是 tldraw 的核心对象,提供了操作画板的所有方法:
| 方法 | 用途 |
|------|------|
| `createShapes()` | 创建形状 |
| `deleteShapes()` | 删除形状 |
| `selectAll()` | 全选 |
| `getSelectedShapeIds()` | 获取选中的形状 ID |
| `getCurrentToolId()` | 获取当前工具 |
| `getZoomLevel()` | 获取缩放级别 |
| `store.getSnapshot()` | 获取状态快照 |
| `store.loadSnapshot()` | 加载状态快照 |
4.2 Shape(形状)
每个形状包含以下基本属性:
```javascript
{
id: 'shape-unique-id',
type: 'geo', // 形状类型
x: 100, // X 坐标
y: 100, // Y 坐标
props: { // 形状属性
// 根据类型不同而变化
}
}
```
4.3 ShapeUtil(形状工具)
ShapeUtil 是形状的定义类,包含:
-
`type`:形状类型标识
-
`props`:属性验证规则
-
`getDefaultProps()`:默认属性
-
`component()`:React 渲染组件
-
`getIndicatorPath()`:选中指示器路径
4.4 事件系统
tldraw 提供丰富的事件监听机制:
| 事件 | 触发时机 | 参数 |
|------|----------|------|
| `create-shape` | 创建形状 | shape |
| `delete-shape` | 删除形状 | shape |
| `select` | 选择变化 | shapes |
| `change-tool` | 工具切换 | tool |
| `point` | 鼠标点击 | info |
| `drag` | 拖动 | info |
| `zoom` | 缩放 | info |
五、关键技术实现
5.1 状态管理
tldraw 使用 ** Zustand ** 作为状态管理库,通过 `editor.store` 访问:
```javascript
// 获取当前状态
const snapshot = editor.store.getSnapshot()
// 恢复状态
editor.store.loadSnapshot(snapshot)
```
5.2 响应式组件
使用 `track` 函数包装的组件会自动响应编辑器状态变化:
```javascript
const CustomToolbar = track(() => {
const editor = useEditor()
const selectedCount = editor.getSelectedShapeIds().length
// 当选中数量变化时,组件会自动重新渲染
return <div>已选择: {selectedCount} 个形状</div>
})
```
5.3 自定义形状渲染
自定义形状通过 `HTMLContainer` 组件渲染 HTML 内容:
```javascript
component(shape) {
return (
<HTMLContainer
style={{ width: shape.props.w, height: shape.props.h }}
>
<div style={{ background: shape.props.color }}>
{/* 自定义 HTML 内容 */}
</div>
</HTMLContainer>
)
}
```
5.4 路径绘制
使用 `Path2D` API 绘制复杂路径:
```javascript
getIndicatorPath(shape) {
const path = new Path2D()
path.moveTo(rx, 0)
path.lineTo(w - rx, 0)
path.arcTo(w, 0, w, ry, rx)
// ...
return path
}
```
六、扩展能力
6.1 自定义工具
可以创建自定义绘图工具:
```javascript
class MyTool extends BaseTool {
static type = 'my-tool'
onPointerDown(info) {
// 处理鼠标按下
}
onPointerMove(info) {
// 处理鼠标移动
}
onPointerUp(info) {
// 处理鼠标抬起
}
}
```
6.2 自定义样式
通过 CSS 变量自定义主题:
```css
:root {
--tl-color-primary: #007bff;
--tl-color-bg: #ffffff;
--tl-color-surface: #f5f5f5;
--tl-font-family: 'Inter', sans-serif;
}
```
6.3 协同编辑
tldraw 支持实时协同编辑,通过 WebSocket 同步状态:
```javascript
// 监听本地变更
editor.store.subscribe((state) => {
// 发送变更到服务器
socket.emit('sync', state)
})
// 接收远程变更
socket.on('sync', (remoteState) => {
editor.store.loadSnapshot(remoteState)
})
```
七、运行指南
7.1 安装依赖
```bash
npm install
```
7.2 启动开发服务器
```bash
npm run dev
```
访问 http://localhost:5173/ 查看示例。
7.3 构建生产版本
```bash
npm run build
```
7.4 预览生产版本
```bash
npm run preview
```
八、总结
本系统全面展示了 tldraw 的核心功能和扩展能力,涵盖了从基础使用到高级自定义的各个方面。通过六个循序渐进的示例,开发者可以快速掌握:
-
**基础集成**:快速搭建绘图应用
-
**数据管理**:初始化数据和状态持久化
-
**UI 扩展**:创建自定义工具栏和面板
-
**事件处理**:监听编辑器事件
-
**形状定制**:创建自定义形状类型
tldraw 以其灵活的架构和丰富的 API,为构建专业绘图应用提供了强大的基础。无论是简单的流程图绘制,还是复杂的协作绘图系统,tldraw 都能满足需求。