contenteditable 是实现轻量级所见即所得编辑框的最直接方案,无需富文本库,但需清洗 HTML 字符串防 XSS,设置可访问性属性、禁用右键菜单,并用 innerHTML 取值后正则过滤危险标签与事件属性。
纯 HTML 表单里塞一个「所见即所得」编辑区,contenteditable 是最直接的解法。它不需要引入 quill、tiptap 或其他重型库,适合快速发布场景(比如后台简易文章草稿框)。但注意:它返回的是 HTML 字符串,不是纯文本,提交前得清洗或转义。
<div> 加 contenteditable="true",再设 role="textbox" 和 aria-label 保证基础可访问性oncontextmenu="return false;"
style="min-height: 200px; outline: none; padding: 12px; border: 1px solid #ddd; line-height: 1.5;" 控制基础样式,避免空白时塌陷designMode 或 iframe —— 过于重,且跨域/沙箱限制多用户输入后,innerHTML 会包含各种标签(<div>、<br>、内联样式),直接 POST 可能引发 XSS 或后端解析失败。必须做最小化清洗。
element.innerHTML.trim(),不是 textContent(后者丢掉所有格式)<script>、<iframe>)和 onerror 等事件属性,例如:html.replace(/
to-markdown 库转换,但注意它不处理嵌套或复杂 HTML!html.replace(/]*>/g, '').trim(),防止只留空标签编辑已有文章再提交,需把服务端返回的 HTML 安全注入到 contenteditable 区域。直接赋值 innerHTML 有风险,且浏览器对换行渲染不一致(<br> vs <p>)。
element.innerHTML
<p> 包裹段落,前端 CSS 加 p { margin: 0.5em 0; }
innerText 回填 —— 它会把 <br> 当成空格,破坏结构style 属性,可在前端用 html.replace(/ style="[^"]*"/gi, '') 批量剥离在 iOS Safari 或 Android Chrome 上,contenteditable 常出现光标卡住、回车无反应、软键盘遮挡等问题,根源是浏览器对焦点和输入事件的处理差异。
overflow: hidden —— 否则 iOS 键盘弹起时编辑区被裁切focus 事件,用 setTimeout(() => element.scrollIntoView({ behavior: 'smooth' }), 100) 强制滚动到可视区<div>,iOS 插入 <br>;如需统一段落行为,可拦截 keydown 中的 Enter,阻止默认并手动插入 <p><br></p>
input 和 blur 做实时保存 —— 移动端频繁触发会导致卡顿或重复提交