必须用 contenteditable 而非 input 实现可关闭 tag,因 input 是替换元素,禁止嵌套子节点;backspace 删除需监听 keydown 并判断光标紧贴 tag 右侧;渲染须防 xss,用 textnode 插入内容。

不能用 <input> 实现可关闭的 tag,这是硬性限制——浏览器会直接剥离嵌套的 HTML,不是 bug,是规范行为。
为什么必须用 contenteditable 而不是 <input>
<input> 是替换元素(replaced element),DOM 标准禁止在其内部插入任何子元素。哪怕你用 JS 强行 appendChild 一个 <span class="tag">foo</span>,Chrome、Firefox 都会立刻丢弃它,只保留纯文本。所以所有「带删除按钮的 tag 输入框」都得基于 div[contenteditable] 构建。
- 别试
input[type="text"] + span伪组合:光标无法自然跨标签移动,Backspace 行为不可控 - 别依赖
selectionStart:在contenteditable中该属性极不稳定,Chrome 常返回0,应改用getSelection().getRangeAt(0) - iOS Safari 对
contenteditable的光标边界检测更敏感,需额外处理range.endOffset === 0 && range.commonAncestorContainer === tagEl这类边界情况
如何让 Backspace 删除整个 tag 而不是单个字符
核心是监听 keydown,判断光标是否紧贴某个 tag 元素的右侧。一旦命中,就 e.preventDefault() 并调用 tagEl.remove()。
- 检查条件:当
e.key === 'Backspace'且getSelection().getRangeAt(0).endOffset === 0,再向上查父节点,确认上一个兄弟节点是class="tag"的元素 - 不要用
innerHTML = ''清空容器:这会销毁所有已绑定的事件(比如 tag 上的 × 按钮 click handler) - 推荐移除方式:直接
tagEl.remove(),现代浏览器(IE10+)均支持,干净不留痕 - 注意顺序:如果用户连续按 Backspace,需确保每次只删一个 tag,避免因 DOM 变化导致后续 range 计算偏移错乱
如何安全渲染和清理用户输入的 tag 内容
tag 里的文字来自用户输入,不能直接插进 innerHTML,否则有 XSS 风险;也不能全用 textContent,因为要保留 tag 元素本身结构。
- 渲染新 tag 时:用
document.createElement('span')创建元素,再用textNode = document.createTextNode(userInput)插入文字内容 - 清除已有 tag 文字内容时:对 tag 内部调用
tagEl.textContent = '',不碰其 DOM 结构 - 批量清理冗余标签(如
<b></b>、<font></font>):用element.replaceWith(element.childNodes)拆解外层标签但保留子内容 - 服务端传来的 HTML 片段含标签?先过
DOMParser解析,再遍历取textContent,别信正则——<script></script>或注释里藏 payload 很容易漏
真正难的不是加 × 按钮,而是光标位置判断的鲁棒性。不同浏览器对 contenteditable 中 range 的计算逻辑有差异,尤其在换行、空格、emoji 后面,建议用 range.getBoundingClientRect() 辅助校验光标是否真在 tag 右侧可视区域内。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











