contenteditable="true"需配合tabindex、paste拦截、enter处理等才能安全可用;否则易致光标丢失或xss漏洞。

直接加 contenteditable="true" 不等于能用——它只是打开浏览器编辑引擎的开关,没配好 tabindex、不拦截 paste、不处理 Enter,轻则光标消失,重则 XSS 漏洞上线。
为什么写了 contenteditable="true" 却点不动或 Tab 进不去
因为 div、p 等非表单元素默认不可键盘聚焦,contenteditable 不会自动加 tabindex。
- 必须显式加
tabindex="0":让元素进入 Tab 链,支持 Ctrl+B/Ctrl+I 等快捷键 - 只写
tabindex="-1":只能靠 JS 调用.focus(),无法键盘切换 - CSS 中若存在
pointer-events: none或user-select: none,也会彻底屏蔽点击
粘贴富文本时为什么带样式甚至 script 标签
这不是 bug,是浏览器原生行为:复制自 Word、微信、网页的内容,默认以完整 HTML 片段插入,包括内联样式、<meta>、不可见字符,甚至 <img onerror="alert(1)">。
- 拦截
paste事件,用event.clipboardData.getData('text/plain')提纯文本最稳妥 - 避免直接用
document.execCommand('insertText'):该 API 已废弃,Chrome 98+ 移除部分命令 - 别依赖
contenteditable="plaintext-only":不是标准值,Firefox 忽略,Safari 行为未统一
input 事件监听不到所有内容变更
input 事件对键盘输入响应及时,但对右键粘贴、拖拽插入、撤销重做(Ctrl+Z/Y)、甚至部分 IME 输入(如中文拼音上屏)常常漏触发。
- 组合监听:
input+compositionend(处理中文输入完成) +blur(防失焦未保存) - 对关键节点使用
MutationObserver监听子节点变动,尤其当允许富文本(如插入图片、链接)时 - 避免在
input回调里直接读innerHTML——若用户粘贴了带<script></script>的 HTML,会执行脚本;应先用textContent或白名单过滤
动态更新 innerHTML 后光标乱跳
Vue/React 中 v-html 或手动改 innerHTML 会导致 DOM 重建,浏览器丢失 selection,光标重置到开头或崩溃。
- 不要全量替换:优先用
textContent更新纯文本,或用insertAdjacentHTML增量插入 - 若必须更新 HTML,需提前缓存光标位置(
window.getSelection().getRangeAt(0)),渲染后再用range.setStart()+selection.addRange()恢复 - 空内容时 fallback 插入一个
(零宽空格),否则range.collapse(false)会静默失败
真正难的不是加属性,而是每次 DOM 变更后手动接管光标、每次粘贴前剥离 HTML、每次回车后统一语义——这些细节不处理,contenteditable 就只是个半成品开关。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











