HTML如何做@提及功能_html @提及at mention功能实现【代码解析】

作者:袖梨 2026-07-29
应监听 keydown + compositionend 组合事件捕获 @ 触发时机,插入 contenteditable="false" 的提及节点并手动设置光标位置,下拉列表需基于光标坐标动态定位且挂载至 body。

怎样使用 contenteditable 捕捉 @ 符号的触发时刻

仅仅监听 input 事件并不可靠,因为用户可能粘贴文字、通过方向键移动光标,或删除中间字符,从而使 @ 出现在不符合预期的位置。真正需要监听的是 keydown + compositionend 这一组合,重点识别按下 @ 键时光标位于行首,或其前一个位置为空白的瞬间。

操作建议:

立即学习前端免费学习笔记(深入);

  • keydown 内部判断 e.key === '@',随后通过 getSelection().getRangeAt(0).startOffset 取得光标位置,并向前检查一个字符是空格、换行还是开头
  • 还必须同时监听 compositionend,否则在中文输入法环境下按完 @ 不会触发,因为输入法会先发送合成事件)
  • 不要在 input 中执行匹配,因为它不能分辨用户输入 @ 与粘贴含 @ 字符串两种行为,容易错误开启下拉列表

如何把 @xxx 显示为可点击标签,同时不破坏编辑流程

不能用纯文本拼接,也不能直接插入 <span> 后还指望光标正常进出——contenteditable 对嵌套内联元素的光标行为很敏感。正确做法是用 document.execCommand('insertHTML', false, ...) 或现代 getSelection().setBaseAndExtent() 配合 Range.insertNode() 插入带 data-mention-idcontenteditable="false"<span>

操作建议:

立即学习前端免费学习笔记(深入);

  • 必须为插入的提及节点设置 contenteditable="false",否则双击后会进入编辑状态并破坏语义
  • 添加 tabindex="0" 以及 role="button",使键盘用户能够聚焦,并通过回车触发详情
  • 样式应采用 display: inline-flex + align-items: center,防止标签换行时被拆分
  • 不要采用 white-space: nowrap,否则长昵称无法折行,还会溢出容器

@ 提及列表如何对齐光标,同时避免遮住输入内容

下拉列表不能依靠固定 top/left定位,而应在每次展示时计算光标相对于编辑区域的绝对坐标,再借助 getBoundingClientRect() 取得光标所在 Text 节点的末端位置,最后叠加滚动偏移。若直接写死 position: absolute; top: 200px ,遇到缩放、滚动或多行编辑时必然发生错位。

操作建议:

立即学习前端免费学习笔记(深入);

  • 使用 window.getSelection().getRangeAt(0).getClientRects() 获取最后一个 DOMRect,其 bottom 可以作为下拉列表显示的基准线
  • 列表容器需要挂载到 body 下面(使用 createPortal 或者 document.body.appendChild),以免被父容器 overflow: hidden 截断
  • 监听 scroll 以及 resize 事件以重新定位,同时加入 throttle 防抖,避免拖动滚动条时出现卡顿
  • 列表项使用 aria-activedescendant 实现键盘导航,不应只支持鼠标操作

为什么完成 @ 提及后继续输入,会出现吞字符或光标跳动

问题根源在于插入的提及节点属于 contenteditable="false",而紧邻它的文本节点依旧可编辑。浏览器对假内联块与真文本之间的光标合并机制并不统一:Chrome中光标可能卡在提及右侧,Safari则可能直接跳到下一行开头。

操作建议:

立即学习前端免费学习笔记(深入);

  • 完成提及插入后,手动通过 Range 把光标放至提及节点后面:先创建新的 Rangerange.setStartAfter(mentionNode),然后再 selection.removeAllRanges() + selection.addRange(range)
  • 不允许用户在提及节点前后连续输入空格,应监听 input,判断光标附近是否出现两个空格,并自动 trim() 后重新设置光标
  • 使用 React/Vue 时,提及节点外部不要再套响应式 wrapper,例如 <div><Mention /></div>),否则会增加额外的 DOM 层级,使光标异常更加严重

输入法兼容才是最难处理的部分:中文用户输入完“@张”但尚未选人便按下空格时,需要判断这是输入法中止信号,而不是真实空格,因此必须结合 compositionstart/compositionend 以及 keydown 的 key 状态进行交叉判断。对于这个边界情况,90% 的开源 mention 库都处理得不够准确。

相关文章

精彩推荐