跳到主要内容

Toolbar

<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 映射:

选中的元素类型加载的组件
textboxTextToolbar(字体、字号、颜色、对齐、阴影、描边…)
imageImageToolbar(滤镜、裁剪、描边、消除笔…)
path / rect / circle / ellipse / triangleFigureToolbar(填充、描边、圆角…)
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 并不兼容

能力PolotnoYdesign
类型专属覆盖方式components={{ TextFill: MyPicker }}(深入到某个子字段)registerToolbarComponent('textbox', MyTextToolbar)(整段替换)或直接修改 TextToolbar 的源码
unstable_registerToolbarComponent✅ 可用对应 registerToolbarComponent(不带 unstable_ 前缀)
downloadButtonEnabled✅ 同名同义
右侧 ActionControls 插槽✅ 完全对齐
TextFontFamily / TextFill / ImageFlip子组件级替换❌(粒度做到了元素类型级别,想改单个控件请修改 TextToolbar 等组件源码,或用 registerToolbarComponent 整段替换)
PageDuration(每页持续时长,动画)❌(没有 Page / 动画概念)
UI 组件库BlueprintAnt Design v6

想实现 Polotno 那种"只替换文字元素的颜色选择器"这类精细改造,在 Ydesign 里可以:

  1. Fork TextToolbar.tsx 作为 MyTextToolbar,只改你关心的那块;
  2. registerToolbarComponent('textbox', MyTextToolbar) 注册回来;
  3. 把不想改的子组件(如字号输入框)直接从 @ydesign/react-editor/toolbar/* 导入使用。

这种"复制一份再局部改"的方式,虽然比 Polotno 的子字段替换稍显笨重,但在维护复杂度上更可控。


下一步