Toolbar
<Toolbar /> 是显示在画布上方的水平工具栏,用来修改选中元素的样式(字体、颜色、滤镜…)、调整层级(上下移、对齐、组合)以及执行撤销/重做、锁定、复制、删除等动作。
工具栏会根据当前选中元素的类型自动切换:选中文字就展示文字工具栏,选中图片就展示图片工具栏,没有选中时就展示默认工具栏。

基本用法
import Toolbar from '@ydesign/react-editor/toolbar';
import Workspace from '@ydesign/react-editor/canvas/workspace';
const App = ({ store }) => {
return (
<div style={{ display: 'flex', height: '100%', flex: 1, flexDirection: 'column' }}>
<Toolbar store={store} />
<Workspace store={store} />
</div>
);
};
Props
type ToolbarProps = {
store: StoreType;
downloadButtonEnabled?: boolean; // 是否显示默认的下载按钮(默认 false)
components?: ToolbarComponents; // 插槽,见下文
};
// 最小示例:打开默认下载按钮
<Toolbar store={store} downloadButtonEnabled />
Toolbar 的内部结构
Toolbar 被分成三个区域:
┌───────────────────────────────────────────────────────────────────────────────┐
│ [History] [类型专属工具栏] [Group] [Position] [Opacity] [Lock]│
│ [Duplicate] [Remove] [ActionControls] │
└───────────────────────────────────────────────────────────────────────────────┘
└─ 左侧公共 ─┘└─ 中部(按元素类型变化)─┘└──── 右侧公共 ────┘└── 业务槽 ──┘
类型专属工具栏(中部)
根据选中元素的 type 自动切换,内部通过 ComponentsTypes 映射:
| 选中的元素类型 | 加载的组件 |
|---|---|
textbox | TextToolbar(字体、字号、颜色、对齐、阴影、描边…) |
image | ImageToolbar(滤镜、裁剪、描边、消除笔…) |
path / rect / circle / ellipse / triangle | FigureToolbar(填充、描边、圆角…) |
cropimage(裁剪模式时) | CropImageToolbar(完成 / 取消) |
| 多选 | ManyToolbar |
| 无选中 | DefaultToolbar(画布尺寸、背景、添加元素入口…) |
右侧公共区(共 6 个 + 1 个业务槽)
永远存在的公共动作:
Group—— 组合 / 拆分Position—— 位置面板(对齐、层级、坐标微调)Opacity—— 不透明度Lock—— 锁定 / 解锁Duplicate—— 复制Remove—— 删除ActionControls—— 业务预留槽,可以放"保存"、"下载"、"发布"等按钮
components 插槽:替换右侧公共区
components 对象里每个 key 都对应右侧一个按钮,传了就会覆盖默认实现:
import { Toolbar } from '@ydesign/react-editor/toolbar';
import { DownloadButton } from '@ydesign/react-editor/toolbar/download-button';
import { Button } from 'antd';
const ActionControls = ({ store }) => (
<div style={{ display: 'flex', gap: 8 }}>
<Button type="primary" onClick={() => save(store)}>
保存
</Button>
<DownloadButton store={store} />
</div>
);
<Toolbar
store={store}
components={{
ActionControls, // 右侧业务槽
}}
/>;
所有可替换的 key:
| Key | 作用 | 默认组件 |
|---|---|---|
ActionControls | 最右侧业务槽,适合放"保存 / 下载 / 发布" | 如果开了 downloadButtonEnabled 就是 DownloadButton |
History | 左侧撤销 / 重做按钮组 | HistoryButtons |
Group | 组合 / 拆分 | GroupButton |
Position | 位置面板 | PositionPicker |
Opacity | 不透明度 | OpacityPicker |
Lock | 锁定 | LockButton |
Duplicate | 复制 | DuplicateButton |
Remove | 删除 | RemoveButton |
去掉某个默认按钮
传一个返回 null 的组件即可:
const Empty = () => null;
<Toolbar store={store} components={{ Opacity: Empty, Lock: Empty }} />;
用内置按钮组合自己的区域
所有按钮都从 @ydesign/react-editor/toolbar/* 单独导出,可以完全自由地拼装:
import { HistoryButtons } from '@ydesign/react-editor/toolbar/history-buttons';
import { DuplicateButton } from '@ydesign/react-editor/toolbar/duplicate-button';
import { RemoveButton } from '@ydesign/react-editor/toolbar/remove-button';
import { LockButton } from '@ydesign/react-editor/toolbar/lock-button';
import { DownloadButton } from '@ydesign/react-editor/toolbar/download-button';
const MyToolbar = ({ store }) => (
<div className="my-toolbar">
<HistoryButtons store={store} />
<DuplicateButton store={store} />
<RemoveButton store={store} />
<LockButton store={store} />
<DownloadButton store={store} />
</div>
);
按元素类型注册新工具栏
当你接入了新的元素类型(例如二维码、条形码、图表),可以调用 registerToolbarComponent 告诉 Toolbar:"当选中这个 type 的元素时,渲染我这个组件":
import { registerToolbarComponent } from '@ydesign/react-editor/toolbar';
import { observer } from 'mobx-react-lite';
const QRCodeToolbar = observer(({ store }) => {
const qr = store.selectedElements[0];
if (!qr) return null;
return (
<div style={{ display: 'flex', gap: 8, padding: '0 12px' }}>
<input defaultValue={qr.data} onChange={e => store.set({ data: e.target.value }, qr)} placeholder="二维码内容" />
</div>
);
});
// 之后只要选中的元素 type === 'qrcode',就会走你的工具栏
registerToolbarComponent('qrcode', QRCodeToolbar);
工具栏组件总是接收 { store } 作为 props。你从 store.selectedElements 取元素、用 store.set(...) 修改即可。
完全替换内置类型工具栏
registerToolbarComponent 也可以用同名 type 直接覆盖内置实现。例如想用一套自己的文字工具栏:
import { registerToolbarComponent } from '@ydesign/react-editor/toolbar';
import { observer } from 'mobx-react-lite';
const MyTextToolbar = observer(({ store }) => {
const el = store.selectedElements[0];
if (!el) return null;
return <div>{/* 自己的 UI —— 颜色、字号、加粗… */}</div>;
});
// 覆盖内置 TextToolbar
registerToolbarComponent('textbox', MyTextToolbar);
被覆盖的类型有:textbox / image / path / rect / circle / ellipse / triangle / many / cropimage。
完整组合示例
import { Toolbar, registerToolbarComponent } from '@ydesign/react-editor/toolbar';
import { DownloadButton } from '@ydesign/react-editor/toolbar/download-button';
import Workspace from '@ydesign/react-editor/canvas/workspace';
import { Button } from 'antd';
import { observer } from 'mobx-react-lite';
// 1) 自定义 ActionControls:保存 + 下载
const ActionControls = ({ store }) => (
<div style={{ display: 'flex', gap: 8 }}>
<Button type="primary" onClick={() => saveToServer(store)}>
保存
</Button>
<DownloadButton store={store} />
</div>
);
// 2) 完全替换文字元素工具栏
const MyTextToolbar = observer(({ store }) => {
/* ... */
});
registerToolbarComponent('textbox', MyTextToolbar);
// 3) 去掉 Opacity 按钮
const Empty = () => null;
// 4) 装配
const App = ({ store }) => (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<Toolbar
store={store}
components={{
ActionControls,
Opacity: Empty,
}}
/>
<Workspace store={store} />
</div>
);
与 Polotno Toolbar 的区别
如果你从 Polotno 迁移过来,有几点API 并不兼容:
| 能力 | Polotno | Ydesign |
|---|---|---|
| 类型专属覆盖方式 | components={{ TextFill: MyPicker }}(深入到某个子字段) | registerToolbarComponent('textbox', MyTextToolbar)(整段替换)或直接修改 TextToolbar 的源码 |
unstable_registerToolbarComponent | ✅ 可用 | 对应 registerToolbarComponent(不带 unstable_ 前缀) |
downloadButtonEnabled | ✅ | ✅ 同名同义 |
右侧 ActionControls 插槽 | ✅ | ✅ 完全对齐 |
TextFontFamily / TextFill / ImageFlip 等子组件级替换 | ✅ | ❌(粒度做到了元素类型级别,想改单个控件请修改 TextToolbar 等组件源码,或用 registerToolbarComponent 整段替换) |
PageDuration(每页持续时长,动画) | ✅ | ❌(没有 Page / 动画概念) |
| UI 组件库 | Blueprint | Ant Design v6 |
想实现 Polotno 那种"只替换文字元素的颜色选择器"这类精细改造,在 Ydesign 里可以:
- Fork
TextToolbar.tsx作为MyTextToolbar,只改你关心的那块; registerToolbarComponent('textbox', MyTextToolbar)注册回来;- 把不想改的子组件(如字号输入框)直接从
@ydesign/react-editor/toolbar/*导入使用。
这种"复制一份再局部改"的方式,虽然比 Polotno 的子字段替换稍显笨重,但在维护复杂度上更可控。