纯前端实现可访问富文本工具栏:选区恢复、命令状态与快捷键
给 contenteditable 加一排加粗、列表和链接按钮,代码量看起来不大,真正使用时却经常出现“点按钮后选区消失”“按钮状态和正文不一致”“键盘用户无法操作工具栏”等问题。工具栏必须和编辑器选区、命令模型与焦点状态一起设计。
一、点击按钮前先保住选区
浏览器在按钮获得焦点时可能折叠或清除编辑器里的 Range。简单做法是在工具栏按钮的 pointerdown 阶段阻止默认焦点切换,或者关闭前保存当前 Range:
let savedRange = null;
editor.addEventListener('keyup', saveSelection);
editor.addEventListener('mouseup', saveSelection);
function saveSelection() {
const selection = getSelection();
if (!selection.rangeCount) return;
const range = selection.getRangeAt(0);
if (editor.contains(range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
真正执行命令前,把 selection 恢复到 savedRange。恢复后仍要检查节点是否还在文档中,撤销、重新渲染或协同更新都可能让旧 Range 失效。
二、把按钮绑定到命令而不是 DOM 细节
工具栏按钮应该调用统一的命令接口,例如 toggleBold、setBlockStyle 或 insertLink。适配层内部可以调用编辑器的命令系统、文档模型或浏览器的 Range API。这样按钮不需要知道当前选中的是文本节点还是段落。
const commands = {
bold: () => applyInline('strong'),
orderedList: () => applyBlock('ol'),
link: () => openLinkDialog()
};
如果使用 document.execCommand,需要清楚它已被标记为过时。它适合轻量兼容层,不适合作为复杂编辑器的长期核心。生产级产品通常应使用成熟的富文本框架,并复用其事务与历史机制。
三、命令状态来自唯一数据源
加粗按钮的按下状态不能因为用户点了一次就永久保持。状态应跟随当前选区重新计算。简单环境可以查询当前格式;使用编辑器模型时,应从选区所在节点及其祖先节点推导:
function syncToolbarState() {
const active = readActiveFormats();
for (const button of toolbar.querySelectorAll('[data-command]')) {
const command = button.dataset.command;
button.setAttribute('aria-pressed', String(active.has(command)));
}
}
selectionchange、输入、撤销和切换到源码模式后都要重新同步。工具栏显示的状态必须和最终文档结构一致。
四、工具栏语义与焦点
一组格式按钮可以放在 role="toolbar" 的容器中,并提供可读名称。按钮不要只显示 B、I 等字符,要有 aria-label,例如“加粗,Ctrl+B”。切换型按钮使用 aria-pressed,菜单型入口使用 aria-haspopup 和 aria-expanded。
完整实现 toolbar 时,方向键应在工具项之间移动,Home 和 End 跳到首尾,Tab 用于离开工具栏。若只使用普通按钮,也要保证 Tab 顺序合理,不能为了视觉整齐把控件全部移出焦点序列。
五、快捷键要有作用域
Ctrl/Cmd+B、I、U 等快捷键只在编辑器或工具栏拥有焦点时处理,不能拦截整个页面的按键。链接弹窗打开后,键盘焦点应进入第一个输入框,Escape 关闭并把焦点还给触发按钮或编辑器。
editor.addEventListener('keydown', (event) => {
if (!(event.ctrlKey || event.metaKey)) return;
if (event.key.toLowerCase() === 'b') {
event.preventDefault();
restoreSelection();
commands.bold();
}
});
六、链接和弹窗的细节
插入链接时保存原选区,选择已有链接时读出 href 与文字。提交前校验协议,避免把 javascript: 当作普通链接写入。取消操作不能改变正文;确认后恢复选区、插入内容并重新同步工具栏。
对于读屏用户,命令执行后通过 aria-live 简短播报“已加粗”或“已插入链接”。不要每次按键都播报,只在命令确实改变文档时反馈。
七、移动端与输入法
移动端浏览器可能收起软键盘并改变 selection,工具栏按钮需要更长的事件处理窗口。输入法组合期间不要立即执行格式命令,至少等 compositionend 后再读取选区,避免把组合中的字符拆开。
八、测试重点
覆盖空选区、跨段选区、点击工具栏后选区是否恢复、命令状态同步、撤销重做、链接取消、Esc 与 Tab 焦点路径以及移动端软键盘变化。把命令适配层和工具栏展示分离后,才能分别测试文档行为与可访问交互。
九、小结
可访问富文本工具栏的核心是保存并恢复选区、用统一命令接口操作文档、从同一状态源同步 aria-pressed、给快捷键限定作用域,并保证弹窗焦点可靠返回。样式只是表层,选区与焦点才是功能稳定的基础。
- 点赞
- 收藏
- 关注作者
评论(0)