问题出在内容抵达pre前已被转义或丢失真实换行符和空格,如后端双重转义为"line1 line2"导致js解析成无换行字符串,或innerhtml拼接时未转义特殊字符而被html解析器截断。

pre 标签里换行和空格不显示,问题出在哪
不是 pre 失效了,是内容根本没带着真实
和连续空格抵达它。浏览器解析 HTML 时,pre 确实启用预格式化路径——但前提是 DOM 中真有那些字符节点。常见断点:
- 后端返回 JSON 字段值为
"line1\nline2"(两个反斜杠),JS 解析后变成字面量"line1 line2",不是真实换行 - 前端用
innerHTML拼接:el.innerHTML = "<pre class="brush:php;toolbar:false;">" + str + "</pre>",若str含或 <code>&,会被 HTML 解析器截断或转义 - Vue 中写
{{ text }}、React 中用{text},但服务端返回的是 HTML 转义后的字符串(如hello<br>world),而非原始换行 - 全局 CSS 重置了
white-space,比如* { white-space: normal; }直接覆盖了pre默认行为
动态注入必须用 textContent,别碰 innerHTML
只要内容来自 API、用户输入或 JS 变量,就绕过 HTML 解析器。这是最常被跳过的一步。
- 正确:
preEl.textContent = apiResponse.body—— 把整个字符串当纯文本节点插入,保留所有、、连续空格 - 错误:
preEl.innerHTML = apiResponse.body—— 若响应含<div>,浏览器会尝试解析;若含 <code>&,可能被二次转义 - Vue 中避免
{{ text }},改用v-text="text";React 中{text}默认走textContent,安全 - 绝对不要在
pre内部再嵌套<div> 或 <code><p></p>,它们会中断预格式化流,破坏缩进对齐只写
不够,三样 CSS 必须配齐
pre默认用等宽字体、white-space: pre、不折行,但现代项目中这三点极易被覆盖或失效。- 字体栈必须显式声明:
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, 'Microsoft YaHei Mono', monospace—— 中文字体不能省,否则中文缩进塌陷 - 空白行为建议用
white-space: pre-wrap:保留缩进与空格,同时允许长行自动折行(比默认pre更实用) - 横向滚动必须配对:
overflow-x: auto+ 父容器max-width: 100%,否则滚动条不触发;禁用word-break: break-all,它会切开 URL 和 base64 - 加
tab-size: 4统一制表符宽度,避免编辑器差异导致错位
展示代码必须嵌套
,且提前转义特殊字符pre单独用缺乏语义,code单独用不保留换行——正确组合永远是<pre class="brush:php;toolbar:false;"><code></code></pre>- 如果后端返回的是未转义的
<div>hello</div>,浏览器会真去解析这个标签,轻则内容消失,重则破坏整个 DOM - 可信来源(如自己写的示例)可用
DOMPurify.sanitize()预处理;不可信输入必须手动转义:→ <code><,>→>,&→& - 给
code加语言类名,比如<code class="js">,为后续语法高亮留钩子 - 移动端记得加
pre { max-width: 100%; },否则代码块横向溢出,用户得左右拖拽才能看全
实际排版中最容易被忽略的,是「内容抵达
pre前是否还带着真实换行符」这个前提——所有 CSS 和嵌套规则,都建立在这个基础上。一旦源头字符串已扁平化,后面怎么调样式都没用。 - 字体栈必须显式声明:











