第一行 第二行 这里的连续空格仍会压缩。
显式换行由
标签表达,CSS 不需要把它改成文本。
// 代码块保留缩进 const result = run();
来源:17golang原创
时间:2026-10-13 10:52:28 308浏览 收藏
富文本预览里最容易混在一起的是两个症状:文本里的换行被浏览器折叠了,或者连续空格被压成了一个空格。展示层只想保留前者、压缩后者时,可以把预览容器设置为 white-space-collapse: preserve-breaks。它会保留 segment break,同时继续折叠连续空白;如果要兼容不支持这个长属性的环境,则先用 white-space: pre-line 作为回退。
官方地址:https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/white-space-collapse
先把规则放在富文本预览容器,而不是去修改已经保存的正文:普通文本节点用preserve-breaks,代码块单独使用pre或break-spaces,浏览器不支持时回退到pre-line。
collapse 是 white-space-collapse 的初始值,连续空白和换行会按普通文本规则折叠。preserve-breaks 保留换行字符,但仍折叠连续空格,适合普通富文本预览。preserve 会同时保留空白和换行,通常更适合代码、诗歌或需要精确排版的文本。值班时可以先看预览容器里到底是什么:如果是一个包含换行字符的文本节点,CSS 空白规则就能决定换行是否显示;如果内容已经被拆成多个段落,段落之间的间距由块级布局和 margin 决定;如果是 ,它本身已经表达了显式换行。三种输入看起来相似,处理入口并不一样。
最容易误判的情况是把一整段内容放在普通 div 中。默认 white-space-collapse 为 collapse,连续空白会合并,文本里的 segment break 也不会按原样占据一行。此时不要先改字符串,把规则临时放到预览根节点上,确认症状是否消失。

white-space-collapse 的关键值可以按“保留什么”来选:collapse 折叠空白;preserve-breaks 只保留换行;preserve 同时保留空白和换行;break-spaces 在保留空白的基础上允许空白本身参与换行。普通富文本预览通常选 preserve-breaks,因为用户输入的换行有意义,但手滑产生的一长串空格不应该撑开布局。
/* 普通富文本:保留换行,连续空格仍按普通文本压缩。 */
.rich-preview {
white-space-collapse: preserve-breaks;
text-wrap-mode: wrap;
}
/* 代码、日志等需要保留每个空格时,使用更严格的策略。 */
.rich-preview pre,
.rich-preview .code-block {
white-space-collapse: preserve;
overflow-wrap: anywhere;
}
这里的 text-wrap-mode: wrap 把换行策略写得更明确;white-space 简写也可以同时表达空白折叠和换行方式。不要把普通预览直接改成 preserve,否则用户复制进来的缩进、连续空格或很长的空白序列可能造成横向溢出。
如果项目需要兼容仍以 white-space 为主的浏览器,可以先写 pre-line,再用特性查询覆盖现代实现。两条规则作用相近,但职责清晰:回退值保障旧环境的基本体验,长属性负责表达当前的精细意图。
/* 回退:保留换行并压缩连续空白。 */
.rich-preview {
white-space: pre-line;
}
/* 支持长属性的浏览器使用更明确的 CSS Text 规则。 */
@supports (white-space-collapse: preserve-breaks) {
.rich-preview {
white-space-collapse: preserve-breaks;
text-wrap-mode: wrap;
}
}
/* 预览中的代码块不跟随普通文本策略。 */
.rich-preview pre {
white-space: pre;
overflow: auto;
}
回滚时只需要移除或覆盖预览容器上的长属性,正文数据不需要被二次改写。若新旧规则在同一选择器上同时出现,后面的声明会覆盖前面的同名属性;因此把 @supports 放在回退规则之后,排查时更容易阅读。
富文本渲染器通常同时输出段落、行内节点、链接、列表和代码块。推荐只给“承载连续文本”的预览根节点设置 preserve-breaks,再给代码块设置自己的空白规则。这样既能保留用户正文的换行,又不会让代码块继承一套不适合它的折叠策略。
第一行
第二行 这里的连续空格仍会压缩。
显式换行由
标签表达,CSS 不需要把它改成文本。
// 代码块保留缩进
const result = run();
如果渲染结果把每一行都包成了独立的 p 或 div,那么块级元素之间的换行不是文本节点里的 segment break,此时要检查渲染器的节点结构、默认 margin 和 CSS reset。反过来,如果节点结构正确却只有普通换行消失,优先检查预览根节点是否仍是 collapse。
排查可以固定成四个信号:样式是否命中、内容是否是文本节点、节点是否被拆成块、浏览器是否支持目标属性。下面的脚本只用于开发环境打印节点类型和计算样式,不参与生产内容清洗。
const preview = document.querySelector('.rich-preview');
// 读取计算样式,确认真正生效的是哪条 CSS 规则。
const style = getComputedStyle(preview);
console.log('collapse=', style.whiteSpaceCollapse);
console.log('white-space=', style.whiteSpace);
// 检查根节点是否存在文本节点,避免把块级结构问题误判为 CSS 问题。
const hasTextNode = Array.from(preview.childNodes)
.some((node) => node.nodeType === Node.TEXT_NODE);
console.log('hasTextNode=', hasTextNode);
// 用特性查询判断当前环境能否识别长属性。
console.log('supports=', CSS.supports('white-space-collapse', 'preserve-breaks'));
如果 CSS.supports 返回 false,让 pre-line 回退即可;如果返回 true 但计算样式仍是 collapse,通常是选择器优先级、加载顺序或组件样式覆盖;如果计算样式正确而换行仍不对,就回到 DOM 结构,检查换行是否已经被编辑器转换成了别的元素。

这类样式调整不应该改变文章、评论或公告的原始内容。上线前把修改范围限制在预览组件的 class;发生横向溢出、代码缩进变化或旧浏览器表现异常时,先回退到 pre-line,再单独给 pre、日志和表格单元格配置规则。
| 现象 | 先看什么 | 处理方式 |
|---|---|---|
| 普通文本换行消失 | 预览根节点的计算样式 | 使用 preserve-breaks,旧环境回退 pre-line |
| 连续空格撑开页面 | 是否误用了 preserve | 普通正文改为 preserve-breaks |
| 代码缩进消失 | 代码块是否被根节点规则覆盖 | 代码块单独使用 pre 或 preserve |
| CSS 正确但换行仍异常 | 文本节点还是多个块级节点 | 检查渲染结构和默认 margin,不修改原始内容 |
最后留一项复盘记录:浏览器兼容性、选择器覆盖、渲染节点形态和代码块例外是否都有对应的测试样例。这样下次再遇到“换行没了”,可以先看计算样式和 DOM,而不是立刻把字符串里的换行替换成 。
preserve-breaks 保留换行但折叠连续空白;preserve 同时保留空白序列和换行。普通富文本预览优先选择前者,代码或需要精确空格的内容再考虑后者。
如果所有换行都已经可靠地转换成 br,显式换行本身不依赖这个属性;但文本节点中的自然换行、复制粘贴产生的空白仍可能需要单独设置规则。
不能。它只控制 CSS 空白展示,不负责 HTML 清洗、脚本过滤、链接安全或编辑器数据规范化。安全处理仍应在渲染前使用项目已有的可信流程。