TOAST UI Editor 工具栏定制完全指南:从默认选项到自定义按钮与状态联动
2026/9/21 16:14:59 网站建设 项目流程

TOAST UI Editor 工具栏定制完全指南:从默认选项到自定义按钮与状态联动

【免费下载链接】tui.editor🍞📝 Markdown WYSIWYG Editor. GFM Standard + Chart & UML Extensible.项目地址: https://gitcode.com/gh_mirrors/tu/tui.editor

TOAST UI Editor(以下简称“编辑器”)以 Markdown 与 WYSIWYG 双模式为核心,而工具栏正是这两类编辑体验的交互枢纽:在 Markdown 语法不直观的所见即所得模式中,绝大多数操作都依赖工具栏完成。本文将围绕官方文档《Toolbar》展开,完整讲解toolbarItems二维数组配置、内建按钮定制、popup弹层选项、el自定义元素、state状态联动与onUpdated回调,并深入仓库源码验证其底层实现,帮助你掌握从“组合默认按钮”到“编写完整自定义工具栏插件”的全套能力。

工具栏选项(Toolbar Option)

编辑器内置了 bold、italic、strike 等总计 16 种工具栏元素。在不指定任何选项时,默认工具栏配置如下:

const options = { // ... toolbarItems: [ ['heading', 'bold', 'italic', 'strike'], ['hr', 'quote'], ['ul', 'ol', 'task', 'indent', 'outdent'], ['table', 'image', 'link'], ['code', 'codeblock'], ['scrollSync'], ], }

从示例代码可以看到,编辑器的工具栏选项以二维数组的形式定义:第一层数组的每个元素是一个工具栏分组(group),分组内的工具栏元素则作为内层数组的元素。各元素按照定义顺序在分组内渲染,而工具栏分组之间以|分隔线符号隔开渲染。

16 种内建工具栏元素速查

结合 toolbarItemFactory.ts 中的createDefaultToolbarItemInfo实现,默认支持的 16 种元素及其默认绑定的命令、tooltip 与 state 如下表所示:

元素名默认命令(command)默认 tooltip(en)状态(state)
heading(弹层,插入标题)Headingsheading
boldboldBoldstrong
italicitalicItalicemph
strikestrikeStrikestrike
hrhrLinethematicBreak
quoteblockQuoteBlockquoteblockQuote
ulbulletListUnordered listbulletList
olorderedListOrdered listorderedList
tasktaskListTasktaskList
table(弹层,插入表格)Insert tabletable
image(弹层,插入图片)Insert image
link(弹层,插入链接)Insert link
codecodeCodecode
codeblockcodeBlockInsert CodeBlockcodeBlock
indentindentIndentindent
outdentoutdentOutdentoutdent
scrollSynctoggleScrollSync

注:scrollSync是一个特殊的切换式元素,其内部渲染为复选框 + 开关(switch)而非按钮,相关实现见 toolbarItemFactory.ts;同时它并非总是显示——当编辑器处于 WYSIWYG 模式或预览样式为tab时,scrollSync会被隐藏(见 toolbar.ts)。此外,超过一行显示宽度的元素会自动收进 “More(更多)” 下拉工具栏中(见 toolbar.ts)。

重新组合默认工具栏

如果想修改默认工具栏的构成,只需在创建编辑器时传入toolbarItems选项:

const editor = new Editor({ el: document.querySelector('#editor'), toolbarItems: [ ['heading', 'bold'], ['ul', 'ol', 'task'], ['code', 'codeblock'], ], });

执行上面的示例代码后,工具栏会按新配置渲染为三个分组。从源码上看,toolbarItems的类型为(string | ToolbarItemOptions)[][](见 editor.d.ts),即每个元素既可以是内建按钮的字符串名称,也可以是一个自定义选项对象——这正是接下来要讲的定制入口。

工具栏按钮定制(Toolbar Button Customizing)

单纯组合默认元素只能满足基础需求。当需要自己创建并添加工具栏按钮时,编辑器提供两大类型的定制选项:一是复用内建按钮 UI,仅重定义图标、tooltip 或弹层行为;二是完全自定义 DOM 元素

内建按钮元素定制(Button Element Customizing)

这种方式仍然使用编辑器内建的按钮渲染逻辑,只覆盖按钮的图标、tooltip 或 popup 行为。对应的选项接口如下:

名称类型说明
namestring工具栏元素的唯一名称,必填
tooltipstring可选。鼠标悬停在工具栏元素上时显示的提示文字。
textstring可选。需要在工具栏按钮元素上显示的文本。
classNamestring可选。应用到工具栏元素上的 class。
styleObject可选。应用到工具栏元素上的行内样式。
commandstring可选。点击工具栏按钮时要执行的命令。与popup选项互斥
popupPopupOptions可选。点击工具栏按钮时希望弹出的弹层。与command选项互斥
const editor = new Editor({ el: document.querySelector('#editor'), toolbarItems: [ [{ name: 'myItem', tooltip: 'myItem', command: 'bold', text: '@', className: 'toastui-editor-toolbar-icons', style: { backgroundImage: 'none', color: 'red' } }] ], // ... });

运行上面的示例后,会生成一个同时应用了classNamestyle的工具栏按钮:按钮包含@文本节点,点击时执行bold命令。

从实现层面看,这类定制走的是 toolbarButton.ts 中的ToolbarButtonComp:按钮渲染为<button>元素,text作为按钮文本,classNamestyle直接拼入 class 与行内样式;点击时如果存在command就调用execCommand(command),否则走弹层分支createPopupInfo(见 toolbarButton.ts)。需要特别注意的是,text只影响按钮文本而不会替换图标字体,示例中通过backgroundImage: 'none'去掉图标字体背景、再用@文本代替图标,是一种常见的定制手法。

popup 选项:自定义弹层

如果希望点击按钮时不是执行命令,而是弹出自己定义的弹层,则使用popup选项。其接口如下:

名称类型说明
bodyHTMLElement要渲染的弹层 DOM 节点,必填
classNamestring可选。应用到弹层元素上的 class。
styleObject可选。应用到弹层元素上的样式。

配置好的弹层节点会在点击工具栏时自动显示在屏幕上,点击其他区域时自动消失。

以编辑器的 color-syntax(颜色选择)插件代码为参考(对应仓库实现见 color-syntax/src/index.ts 与 index.ts):

const container = document.createElement('div'); // ... const button = createApplyButton(i18n.get('OK')); button.addEventListener('click', () => { // ... eventEmitter.emit('command', 'color', { selectedColor }); eventEmitter.emit('closePopup'); }); container.appendChild(button); const colorPickerToolber = { name: 'color', tooltip: 'Text color', className: 'some class', popup: { className: 'some class', body: container, style: { width: 'auto' }, }, };

示例代码将弹层要展示的元素放入变量container。该元素包含一个按钮,点击时执行color命令并关闭弹层。这里展示了自定义弹层与编辑器通信的两条关键途径:

  • 需要执行命令时,通过eventEmitter.emit('command', 'color', { selectedColor })发出command事件;
  • 需要关闭弹层时,发出closePopup事件。

从底层实现看,自定义弹层最终会渲染为CustomPopupBody,由createPopupInfo('customPopupBody', ...)处理(见 toolbarItemFactory.ts),而弹层的打开/关闭状态由 Toolbar 组件的showPopup状态统一管理,点击弹层外部区域会触发hidePopup(见 toolbar.ts)。color-syntax 插件的完整示例可对照 color-syntax 插件源码 学习。

工具栏元素定制(Toolbar Item Customizing)

如果不使用内建按钮 UI,而是希望完全自己控制渲染元素,则需要指定el选项:

const myCustomEl = document.createElement('span'); myCustomEl.textContent = '😎'; myCustomEl.style = 'cursor: pointer; background: red;' myCustomEl.addEventListener('click', () => { editor.exec('bold'); }); const editor = new Editor({ el: document.querySelector('#editor'), toolbarItems: [ [{ name: 'myItem', tooltip: 'myItem', el: myCustomEl, }] ], // ... });

由于el选项接收的是一个完整的 DOM 元素,因此点击行为、style、class 等都必须由开发者自行设置。运行示例后,工具栏会渲染出自定义的😎元素,点击执行bold命令。

在实现上,带el的工具栏项会渲染为CustomToolbarItem,它把自定义元素appendChild进一个带toolbar-item-wrapperclass 的容器中,并自动为其挂上 tooltip 与 popup 事件监听(见 customToolbarItem.ts)。分组渲染时,ToolbarGroup会依据“是否存在el”来区分渲染CustomToolbarItem还是ToolbarButton(见 toolbarGroup.ts)。

el外,ToolbarCustomOptions还额外支持hiddenonMounted两个仅在自定义元素下可用的选项(见 ui.d.ts),其中onMounted(execCommand)会在元素挂载完成后被调用,可用来绑定需要执行命令的复杂交互(如 scrollSync 元素的自定义复选框逻辑,见 toolbarItemFactory.ts)。

工具栏状态变更(Change Toolbar Item State)

编辑器可以根据当前光标所在位置对应的节点类型,通过改变工具栏元素样式来“点亮”对应按钮。例如,当光标位于加粗文本strong节点上时,bold工具栏元素会被激活(追加active类)。

若希望自定义的工具栏元素也能随光标位置切换状态,则需要配置state选项:

const editor = new Editor({ el: document.querySelector('#editor'), toolbarItems: [ [{ name: 'myItem', tooltip: 'myItem', command: 'bold', text: '@', className: 'toastui-editor-toolbar-icons', style: { backgroundImage: 'none', color: 'red' }, // 光标位于 `strong` 节点时,该工具栏元素会被追加 'active' 类 state: 'strong', }] ], // ... });

当工具栏按钮依据state被激活时,编辑器会为其追加activeCSS 类,之后即可基于该类编写自定义激活样式。按钮激活类拼接发生在 toolbarButton.ts:${item.className || ''}${active ? ' active' : ''}

state 列表

只有使用下列 state 值,才能改变工具栏元素的激活状态:

  • heading:标题
  • strong:加粗
  • emph:斜体
  • strike:删除线
  • thematicBreak:水平分隔线
  • blockQuote:引用块
  • bulletList:无序列表
  • orderedList:有序列表
  • taskList:任务列表
  • table:表格
  • code:行内代码
  • codeBlock:代码块

从类型定义看,state的类型是ToolbarStateKeys,即上述键的联合类型,同时底层状态映射ToolbarStateMap还额外包含indentoutdent两项(见 ui.d.ts),与默认元素indent/outdent一一对应。

状态联动的底层原理

工具栏状态不是凭空产生的,而是由 WYSIWYG 编辑器内的 ProseMirror 插件实时计算并广播的。在 toolbarState.ts 中,toolbarStateHighlight插件在每次文档选择(selection)更新时调用getToolbarState(selection, doc, schema),遍历光标范围内的节点:

  • 位于listItem时,根据node.attrs.task判定为taskList或父级列表类型;
  • 节点类型包含table时归一化为table
  • strongstrikeemphcode四种 mark 类型,检查选区两侧是否存在对应 mark;
  • 其余节点类型直接作为 state 键,标记为{ active: true }

计算结果通过eventEmitter.emit('changeToolbarState', { toolbarState })广播出去。而在 UI 侧,buttonHoc.ts 中每个带state的按钮都会listen('changeToolbarState'),从toolbarState[item.state]中取出{ active, disabled }并更新自身状态——这就是光标移动时按钮实时点亮的完整调用链。

onUpdated() 选项:自定义元素的状态回调

使用el选项创建工具栏元素时,由于编辑器无法直接操纵你传入的 DOM,必须借助onUpdated回调来响应状态变化:

const myCustomEl = document.createElement('span'); myCustomEl.textContent = '😎'; myCustomEl.style = 'cursor: pointer; background: red;' myCustomEl.addEventListener('click', () => { editor.exec('bold'); }); const editor = new Editor({ el: document.querySelector('#editor'), toolbarItems: [ [{ name: 'myItem', tooltip: 'myItem', el: myCustomEl, state: 'strong', onUpdated({ active, disabled }) { if (active) { myCustomEl.style.background = 'green'; } else { myCustomEl.style.background = ''; } } }] ], // ... });

onUpdated()接收一个包含activedisabled状态的对象作为参数,可据此为元素追加样式或定义任意行为。其触发时机在 customToolbarItem.ts:当activedisabled与前一次渲染不同时,调用item.onUpdated?.({ active, disabled })。注意disabledactive可能同时出现(例如列表内indent/outdent的禁用态),状态对象类型见 ui.d.ts。

动态增删工具栏项:insertToolbarItem 与 removeToolbarItem

除初始化配置外,编辑器还提供了运行时 API:

  • editor.insertToolbarItem({ groupIndex, itemIndex }, item):向指定分组的指定位置插入一个工具栏项(字符串或选项对象);
  • editor.removeToolbarItem(itemName):按name移除工具栏项。

两个 API 的类型声明见 editor.d.ts,实现位于 toolbar.ts:insertToolbarItem会先通过createToolbarItemInfo把字符串规范化为选项对象,再在对应分组splice插入;若分组索引越界则新建分组。结合 example15-customizing-toolbar-buttons.html 可以看到完整用法——该示例在初始化工具栏后,又通过insertToolbarItem在第一个分组的最前面动态插入了一个自定义的@按钮(并配合firstclass 实现红色样式)。

完整实战示例

官方提供了可运行的示例 example15-customizing-toolbar-buttons.html,其中综合展示了三种定制手段:

function createLastButton() { const button = document.createElement('button'); button.className = 'toastui-editor-toolbar-icons last'; button.style.backgroundImage = 'none'; button.style.margin = '0'; button.innerHTML = `<i>B</i>`; button.addEventListener('click', () => { editor.exec('bold'); }); return button; } const editor = new toastui.Editor({ el: document.querySelector('#editor'), previewStyle: 'vertical', height: '500px', initialValue: 'The first and last buttons are customized.', toolbarItems: [ ['heading', 'bold', 'italic', 'strike'], ['hr', 'quote'], ['ul', 'ol', 'task', 'indent', 'outdent'], ['table', 'image', 'link'], ['code', 'codeblock'], // 用 el 选项定制“最后一个”按钮 [{ el: createLastButton(), command: 'bold', tooltip: 'Custom Bold' }] ] }); editor.insertToolbarItem({ groupIndex: 0, itemIndex: 0 }, { name: 'myItem', tooltip: 'Custom Button', command: 'bold', text: '@', className: 'toastui-editor-toolbar-icons first', style: { backgroundImage: 'none' } });

该示例在编辑器初始化后动态插入“第一个”按钮(使用insertToolbarItem),并在toolbarItems中配置了“最后一个”按钮(使用el自定义元素),配合样式表中.toastui-editor-defaultUI button.first { color: red; }.last { color: orange; }的定制样式,直观演示了本节所有 API 的组合用法。

小结

工具栏是 TOAST UI Editor 中交互密度最高的组件。通过toolbarItems二维数组可以自由重组 16 种内建元素;通过command/popup选项可以复用内建按钮 UI 定制点击行为;通过el选项可以完全接管渲染元素;通过state+onUpdated可以实现随光标位置实时点亮的动态状态;再配合insertToolbarItem/removeToolbarItem两个运行时 API,即可打造出高度贴合业务场景的定制工具栏。

【免费下载链接】tui.editor🍞📝 Markdown WYSIWYG Editor. GFM Standard + Chart & UML Extensible.项目地址: https://gitcode.com/gh_mirrors/tu/tui.editor

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询