pre标签仅忠实渲染原始空白字符,是否生效取决于内容传入前是否保留真实 、 和连续空格;常见错误包括服务端返回转义字符串"line1 line2"或前端误用innertext/innerhtml。

pre 标签本身不“处理”文档,只忠实地渲染已有空白字符
它不会修复丢失的换行、还原被模板转义的
、也不能把全角空格自动对齐。关键判断是:pre 是否生效,取决于内容抵达它之前是否还带着真实的
、 和连续空格。如果你看到一行平铺的文本,问题大概率出在服务端返回的是字符串 "line1\nline2"(两个反斜杠),而不是字节 "line1
line2";或者前端用 innerText 赋值,直接折叠了所有空白。
动态注入时必须用 textContent,禁用 innerHTML 拼接
常见错误是写 el.innerHTML = "<pre class="brush:php;toolbar:false;">" + rawString + "</pre>" —— 若 rawString 含 或 <code>&,会被解析为标签或实体,破坏结构;若含未转义的
,HTML 解析器会忽略它(因为不是 DOM 文本节点中的真实换行)。正确做法是:
- 先确保 DOM 中已存在
<pre class="brush:php;toolbar:false;"></pre>元素(静态写死或 JS 创建) - 直接赋值:
preEl.textContent = apiResponse.body—— 这绕过 HTML 解析,作为文本节点保留 - Vue 中避免
{{ text }},改用v-text="text";React 中{text}默认走textContent,安全 - 绝对不要在
pre内部再嵌套<div> 或 <code><p></p>,它们会中断预格式化流等宽字体 +
white-space+overflow-x缺一不可只写
<pre class="brush:php;toolbar:false;"></pre>不够。浏览器默认white-space: pre,但容易被 reset.css 覆盖;默认用系统等宽字体,但中文可能 fallback 到不支持的字体导致缩进塌陷;默认不折行也不滚动,长行直接撑破容器。必须显式加固:- 字体:设
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, 'Microsoft YaHei Mono', monospace—— 优先包含中文字体,避免只写monospace - 空白行为:加
white-space: pre-wrap(保留缩进/空格,允许长行换行)或保持pre(严格原样,需配滚动) - 横向溢出:
overflow-x: auto必须配max-width: 100%父容器,否则滚动条不触发 - Tab 宽度:加
tab-size: 4统一解释,比混用 Tab 和空格更可控
展示 HTML 代码时,必须提前转义
和 <code>>pre不做任何过滤,它照单渲染你给的每个字符。如果后端返回的是"<div>hello</div>",那浏览器就真显示那串文字;但如果返回的是未转义的<div>hello</div>,浏览器会尝试解析这个标签,轻则内容消失,重则破坏整个页面 DOM 结构。- 可信来源(如自己写的示例代码):用工具提前转义,例如
DOMPurify.sanitize(htmlString, { USE_PROFILES: { html: false } })或简单正则.replace(/, '/g, '>') - 不可信来源(如用户提交的日志):必须服务端转义,或前端用
textContent注入(此时显示的是原始字符串,非渲染结果) - 别在
pre里写<pre class="brush:php;toolbar:false;"><code></code></pre>套娃,语义混乱且易出错
真正难的不是写对
pre标签,而是追踪空白字符从数据源 → 传输 → 模板 → JS 注入 → DOM 渲染的每一步是否被意外剥离或转义。一个JSON.stringify()、一次innerText赋值、一段没关掉的 Django|safe,都可能让格式在最后一环崩掉。 - 字体:设











