富文本编辑器插入代码块须通过 DOM 操作创建安全节点并插入光标位置,禁用 innerHTML 拼接和 execCommand;需转义 HTML 实体、处理不可见字符、统一换行符,并借助编辑器扩展机制支持语法高亮。

富文本编辑器里插入代码块,不是把 <pre><code> 直接粘进去就完事——它会被解析成普通 HTML 元素,甚至触发脚本执行或格式错乱。必须走 DOM 操作路径,且要绕过浏览器的自动解析逻辑。
contenteditable 区域内插入预格式化代码块
在 contenteditable 元素中插入代码块,不能用 innerHTML +=,否则尖括号会被解析、换行被忽略、样式丢失。核心是:先创建安全节点,再插入到光标位置。
- 用
document.createTextNode()包裹已转义的字符串(如<div>→<div>),再塞进 <code><pre><code>结构里 - 获取当前选区:
const selection = window.getSelection();若无选区,需 fallback 到光标末尾插入 - 用
range.insertNode()把完整<pre><code>...</code></pre>节点插进去,而不是拼字符串 - 插入后调用
range.collapse(false)把光标移到新块后面,避免用户继续输入覆盖结构 - 所有浏览器都会对传入的
htmlString做二次解析,<script>可能被执行,<img src=x onerror=alert(1)>不会过滤 -
<pre>里的换行在某些旧版 Safari 中仍被压缩成单行,white-space: pre-wrap也不一定生效 - 如果目标编辑器用了 iframe +
designMode,父页面调用execCommand会失效,必须切到iframe.contentDocument上执行 - 不推荐用于插入带语言标识的高亮代码块(如
class="js"),因为多数编辑器会 strip 掉 class 或重写标签结构 - Quill 推荐用自定义 Blot:继承
Parchment.Embed,把<pre><code>封装为一个不可拆分的 Block,再注册进编辑器 - TipTap 的
commands.insertContent()接收的是 ProseMirror JSON node,不是 HTML 字符串;需先用generateHTML()或parseHTML()转换,且确保 parser 白名单允许pre和code标签 - 无论哪种库,插入前必须对原始代码内容做 HTML 实体转义——不能依赖库自动处理,尤其当代码来自用户粘贴或后端返回时
- 如果要支持语言检测和高亮,别在插入时硬编码
class="python",而应统一走编辑器的“代码块节点”扩展机制(如 Quill 的code-block格式),由渲染层动态加 class - 从 VS Code 或 Word 复制的代码常含零宽空格(
)、软连字符(‐)、全角引号(“”),这些在textContent插入时不会被过滤,但会导致 JS 语法错误 -
<pre>默认用等宽字体,但缩进若混用 Tab 和空格,tab-sizeCSS 属性未设时,Chrome 和 Firefox 渲染宽度不同 - 服务端返回的代码字符串若含 Windows 换行(
\r\n),部分编辑器解析时会多出空行;建议统一 normalize 为\n - 插入后检查
element.innerText是否与预期一致——这是验证是否被意外解析的最快方式
用 document.execCommand 插入代码块的局限性
document.execCommand('insertHTML', false, htmlString) 看似方便,但实际踩坑密集:
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
第三方编辑器(Quill / TipTap)中插入代码块的正确姿势
这类库封装了底层 DOM 操作,但暴露的 API 并不都支持原生插入 <pre><code>。强行用 dangerouslyPasteHTML 或 clipboard.dangerouslyPasteHTML 仍有 XSS 风险。
立即学习“前端免费学习笔记(深入)”;
最易被忽略的细节:空格、换行与不可见字符
哪怕 DOM 插入完全正确,代码块显示仍可能错位或报错,问题往往出在内容本身:


















