pre标签本身能保留换行和Tab,但问题常出在数据未真正进入DOM、HTML转义干扰、CSS重置覆盖white-space属性、字体非等宽或tab-size未设置、用户输入未转义及换行符类型不统一。

pre 标签本身就能保留换行和 Tab,但实际页面里缩进错位、换行消失、中文塌陷——问题几乎都不出在 pre 上,而是内容没真正到达它,或字体/样式没兜住排版。
为什么pre里换行和Tab没显示出来
根本不是标签失效,而是换行符、Tab 字符压根没进 DOM:
- 服务端模板(如 EJS、Django)对插值内容做 HTML 转义,
"line1\nline2"变成字面量line1\nline2,浏览器收不到真实\n - 用
innerHTML动态写入时,若字符串含<或&未转义,会被解析为标签,破坏结构 - HTML 文件被压缩(minified),源码里本就没有换行和缩进,
pre无从保留 - CSS 重置样式覆盖了默认
white-space: pre,比如某 reset.css 设了white-space: normal
验证方法:打开开发者工具 → Elements 面板 → 展开 pre → 看里面是不是真有换行符节点(#text 节点之间有回车)。没有?问题出在数据注入环节。
动态插入内容时必须用textContent而非innerHTML
textContent 绕过 HTML 解析器,确保原始字符串里的 \n、\t 直接变成文本节点;innerHTML 会把它们当 HTML 标签或实体处理,风险高。
立即学习“前端免费学习笔记(深入)”;
正确写法:
const el = document.querySelector('pre');
el.textContent = `function hello() {\n\tconsole.log("Hi");\n}`;
错误写法(即使字符串已转义,仍可能触发解析):
el.innerHTML = `function hello() {<br> console.log("Hi");<br>}`;
注意:textContent 不会解析任何 HTML,所以 <、> 会原样显示——如果要展示代码,需提前对特殊字符做 HTML 实体转义(、<code>>、&)。
等宽字体 + tab-size 是缩进对齐的前提
pre 保留了 Tab 字符数量,但能否对齐,取决于字体是否等宽。非等宽字体(如 Arial、system-ui)会让空格、英文、中文宽度不一致,导致缩进塌陷。
必须显式设置:
pre {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, 'Microsoft YaHei Mono', monospace;
tab-size: 4;
}
关键点:
- 别只写
monospace——某些旧系统 fallback 到不支持中文的等宽字体,造成中文字体降级 -
tab-size: 4统一 Tab 显示宽度,避免编辑器差异(如 VS Code 默认 2,Sublime 默认 4)导致错位 - 如果
pre内嵌了code,记得给code单独设等宽字体,否则继承链可能被父级 CSS 中断
pre 内容来自用户输入时,HTML 转义不可省略
pre 不做任何过滤,照单渲染你给的每个字符。如果后端返回的是未转义的 <div>hello</div>,浏览器会尝试解析这个标签,轻则内容消失,重则破坏整个 DOM。
安全做法:
- 服务端返回前做转义:
<→,<code>>→>,&→& - 前端 JS 注入前手动转义(可用简单正则:
str.replace(/[&<>"']/g, c => `&${{ '&': 'amp', '<': 'lt', '>': 'gt', '"': 'quot', "'": '#39' }[c]};`)</li> <li>绝对不要直接拼接用户输入到 <code>innerHTML,哪怕外面包着pre
最易被忽略的细节:换行符类型(\n vs \r\n)在不同系统下不一致,服务端统一归一化为 \n,前端才好控制 white-space 行为。



















