← dompdf.js Studio

伪元素与 content 渲染:装饰与序号的正确姿势

伪元素是 CSS 里最灵巧的装饰工具:段落前的引号、列表的序号、标题下的分隔线、价格前的货币符号,都可以用 ::before 与 ::after 配合 content 属性实现,不需要在 HTML 里堆砌多余的标签。导出 PDF 时,伪元素的支持程度直接决定模板的写法:支持的库可以用纯 CSS 完成装饰,不支持的库只能退回手写标签,模板复杂度完全不同。dompdf.js 支持 ::before 与 ::after 伪元素及其 content 属性,包括字符串、attr()、计数器(counter)等常用形式,网页上的伪元素装饰在 PDF 中原样呈现。本文系统讲解伪元素的基础用法、content 的多种取值、计数器实现自动序号、装饰类伪元素的实战写法,以及支持范围与常见限制,帮助开发者用最少的 DOM 结构做出完整的排版装饰,模板更干净、维护更轻松,数据与装饰彻底解耦,多页文档的序号体系自动维护。

伪元素的价值与应用场景

伪元素让装饰不依赖额外标签:段落前的装饰符号、标题两侧的分隔线、列表项的序号圆点、引用块的引号标记,全部由 CSS 完成,HTML 结构保持语义纯净。对 PDF 模板来说,结构越干净越容易维护,装饰样式集中在一处,改动只动样式不动结构,这是伪元素最核心的价值,模板开发效率明显提升。

在 PDF 场景里,伪元素还有一层特殊意义:模板通常是动态拼接的 HTML,数据填充时很难为每条数据手工插入装饰标签,而伪元素挂在固定的类名上,数据怎么变装饰都不会丢。比如合同条款前的序号、报告章节前的编号,用伪元素实现后,模板代码与数据逻辑完全解耦,新增条目自动获得装饰。

伪元素也承担内容语义的职责:装饰性内容放在伪元素里不会进入阅读器的内容流,正文语义不被干扰;但如果 content 里是真实信息(如序号、价格),要考虑 PDF 的文字提取与搜索是否包含伪元素内容,这决定了装饰与信息两种用途的写法差异,用途不同写法不同。

::before 与 ::after 基础用法

::before 与 ::after 分别生成元素内容之前与之后的内联盒子,必须配合 content 属性使用,content: "" 的空字符串也合法,常用于生成纯装饰元素(如分隔线)。伪元素默认是行内的,设置宽高需要先改成块级或行内块,display 与 position 等属性在伪元素上完全可用,布局能力不受限。

伪元素挂在哪个元素上,就继承哪个元素的字体与颜色等可继承属性;内容长度不受限制,但伪元素的内容不参与父元素的文本语义,PDF 的文字提取可能不包含它们,这是装饰与信息用途分界的关键点,需要提取文字的序号建议用真实元素,按用途选择写法。

伪元素也可以携带自己的样式:字号、颜色、字重、背景都可以独立设置,装饰与正文观感分离。比如章节标题前的编号用品牌色与更大字号,正文保持默认样式,一个类名即可完成整套装饰,模板代码量显著减少,样式调整也集中在伪元素规则里,一处修改全局生效。

代码示例:序号、引号与分隔线

引号类装饰用 content 直接写字符,注意引号字符在模板字符串里的转义,中文引号与英文引号按文档语言选择;价格前的货币符号用伪元素挂在类名上,数据字段保持纯数字,模板与数据彻底分离,货币符号换币种只改一处样式,全文档同步更新,比在数据层拼接字符串干净得多,也避免了数据与显示格式耦合。

计数器是伪元素的高阶用法:counter-reset 在父容器初始化,counter-increment 在每个目标元素上递增,content: counter(section) 输出当前值。章节序号、条款编号、图表编号都可以用计数器自动生成,新增或删除条目时序号自动重排,不用手工维护数字,多页文档也不会出现序号错乱,文档结构变序号自动变。

分隔线类装饰用空 content 加 display: block 与背景色实现,比 border 更灵活:长度、位置、渐变都可以精细控制;配合 margin 与 width 可以做出短横线、居中短线等常见装饰,标题下的分隔线是文档类 PDF 最高频的装饰需求,一行规则即可完成,模板干净、效果专业。

<style>
  .quote::before { content: '“'; font-size: 2em; color: #888; }
  .quote::after { content: '”'; font-size: 2em; color: #888; }
  .divider::after { content: ''; display: block; height: 2px; background: #333; margin-top: 8px; }
  .price::before { content: '¥'; font-weight: bold; }
  .doc { counter-reset: section; }
  .section-num { counter-increment: section; }
  .section-num::before { content: counter(section) '. '; color: #1a56db; font-weight: bold; }
  .required::after { content: ' *'; color: red; }
</style>
<div class="doc">
  <h2 class="section-num">第一章 项目背景</h2>
  <p class="quote">引用文本前后自动添加中文引号</p>
  <p class="price">1,299.00</p>
  <p>必填项 <span class="required">公司名称</span></p>
  <div class="divider"></div>
</div>

content 属性的多种取值

content 支持字符串、attr()、counter()/counters()、引号(open-quote)以及图片(url)等多种取值,可以组合使用。字符串最常用;attr(href) 可以输出元素的属性值,比如把链接的 URL 打印在 PDF 里,适合需要标注链接地址的打印文档;open-quote 自动配对引号,配合引号样式定义,语义清晰。

counters() 用于嵌套计数器:多级列表的编号(1.1、1.2.1)可以用 counters(section, '.') 输出,层级自动拼接,多级条款、多级标题的编号体系用这一条规则就能完整实现,避免在数据层手工拼编号字符串,编号永远与结构一致,结构调整编号自动跟随,维护成本趋近于零。

content 里也可以放图片 url,用于图标类装饰;但图片受加载时序影响,生成 PDF 前要确保图片资源就绪。装饰性图片优先用 SVG 或小体积位图,嵌入模板或作为静态资源加载,体积与清晰度兼顾;纯色图标用字符或 CSS 形状更轻量,性能与观感都更好,按装饰复杂度选择实现方式。

支持范围与限制:哪些写法要避开

dompdf.js 对伪元素的支持覆盖 ::before/::after 与常用的 content 取值,但浏览器端的完整 CSS 能力不可能全部移植。模板开发时应避免依赖过于前沿的写法,比如复杂的 attr() 表达式、多级嵌套的极端场景、依赖运行时状态的样式,这类写法在网页上正常,在 PDF 里可能静默失效,写模板时优先用基础写法。

伪元素的内容不参与 PDF 的文本层提取与搜索,这是最重要的限制:如果 PDF 需要被检索、复制或做文字处理,序号、编号这类信息性内容应该用真实元素渲染,装饰性内容才适合用伪元素。按信息用真元素、装饰用伪元素的原则划分,两种需求都能满足,不会顾此失彼。

还有一些细节限制:伪元素无法携带事件与交互,PDF 场景无影响;部分高级选择器与伪元素组合在静态 PDF 里没有意义,不需要投入精力;遇到渲染不一致时,先做最小化验证,把伪元素规则单独测试,快速定位是支持问题还是写法问题,比盲目调整模板高效得多,排查路径清晰。

常见问题与最佳实践

Q: 伪元素在 PDF 里没有显示?A: 检查是否写了 content 属性(空字符串也算);确认选择器正确、样式未被覆盖;再验证 content 里的特殊字符是否正确转义;用浏览器渲染同一模板对比,浏览器正常而 PDF 缺失时,多半是写法超出了支持范围,换成更基础的写法即可解决。

Q: 计数器序号没有递增?A: 确认 counter-reset 与 counter-increment 的挂载位置正确:reset 在父容器,increment 在目标元素上,且目标元素必须是 increment 规则选择器命中的元素;多个独立编号体系用不同的计数器名称,互不干扰,序号体系就稳定可靠,检查时按这三个位置逐个核对。

最佳实践小结:装饰用伪元素、信息用真实元素;content 用字符串与计数器;特殊字符注意转义;生成前用浏览器对比验证;复杂的装饰降级为基础写法。按这五条实践,伪元素在 PDF 模板里既能发挥装饰能力,又不会成为坑,模板干净、输出稳定,团队协作也顺畅。

⚡ 现场演示(点击生成 PDF)

下面的按钮用 dompdf.js 在浏览器端实时生成 PDF,无需后端:

Hello from dompdf.js!

这是由 dompdf.js 渲染的示例 PDF 内容。