← dompdf.js Studio

@page 分页媒体详解:dompdf.js 页眉页脚与页码

一份正式的 PDF 几乎都要有页眉页脚和页码:公司名称、文档标题、页码、总页数、机密等级,这些元素如果靠每页手工插入,既繁琐又容易出错。在 dompdf.js 中,这些通过 @page 分页媒体规则统一配置:@page 控制页面尺寸与边距,@top-center、@bottom-right 等边距盒放置页眉页脚内容,counter(page) 与 counter(pages) 生成页码与总页数,一次配置所有页面自动生效,页面数量变化时页码自动更新,无需任何手工维护。本文从 @page 规则的基本语法讲起,覆盖 size 与 margin 的写法、六个边距盒的位置与用途、页码计数的实现细节,配合完整代码示例演示一份带页眉页脚页码的正式文档,并讲解 page-break 分页控制与孤儿寡行控制,最后给出常见问题排查清单,帮你把 PDF 的页面级细节一次做到位。示例代码覆盖了企业文档最常见的页眉页脚需求,复制后按需修改即可直接使用。

@page 规则:尺寸与边距

@page 是 CSS 分页媒体的核心规则,用来描述页面本身的属性。size 定义页面尺寸:size: A4 使用标准纸张,size: A4 landscape 使用横向,也可以写具体尺寸 size: 210mm 297mm;dompdf.js 支持标准纸张规格,页面尺寸决定输出文件的物理大小。

margin 定义页面边距:@page { margin: 20mm } 四边统一,也可以分别设置 margin-top、margin-bottom、margin-left、margin-right。边距即页面内容区的边界,正文与页眉页脚都在这套边距体系内排布,边距值直接影响每页可容纳的内容量。

理解尺寸与边距的关系很重要:内容区高度等于页面高度减去上下边距,长文档的分页数量直接受此影响;边距过小页面显得拥挤,边距过大浪费纸张,建议正文边距 15-25mm,装订场景在装订侧额外留白,输出效果最接近印刷规范。

除了 A4,常见的还有 Letter、A3、A5 等规格,页面尺寸决定内容区大小,选型时要结合文档用途:合同与报表用 A4,海报用 A3,说明书小册子用 A5;页面规格一旦确定,模板的版式设计都围绕它展开。

边距的另一个作用是给装订留空间:双面打印或装订成册的文档,装订侧边距应比外侧宽 5-10mm,避免装订后内容被遮挡;这种细节在正式交付的文档中很常见,提前规划可以避免后期整体调整版式。

边距盒:页眉页脚的容器

边距盒(margin boxes)是 @page 规则内部定义的特殊区域,位于页面边距之中,用于放置页眉页脚内容。常用的是上边距的 @top-left、@top-center、@top-right 与下边距的 @bottom-left、@bottom-center、@bottom-right,共六个位置,分别对应页眉页脚的左中右三列。

边距盒内可以写文字、设置字体与颜色,内容会自动居中或对齐;典型用法:@top-center 放文档标题,@bottom-left 放公司名称,@bottom-right 放页码。边距盒只在对应位置存在内容时占用空间,模板可以按需启用,互不影响。

边距盒的样式支持与普通元素一致:font-size、font-weight、color 等属性都可以设置;页眉页脚通常使用小字号(9-11px)与浅色(如 #666),与正文形成层次,既清晰又不喧宾夺主,符合正式文档的排版惯例。

六个边距盒可以组合出丰富的页眉页脚:页眉放标题与章节名,页脚放公司信息、页码与机密等级;每个位置的内容要简洁,避免与正文争抢注意力,页眉页脚的字体建议比正文小一号,颜色略浅。

边距盒内也支持简单样式控制,比如给页眉加底部边框线、给页脚加顶部边框线,让页眉页脚与正文之间形成视觉分隔;边框线颜色用浅灰即可,太深会显得笨重,与整体文档风格保持一致。

页码与总页数计数

页码通过计数器生成:counter(page) 表示当前页码,counter(pages) 表示总页数,在边距盒的 content 属性中引用:content: counter(page) ' / ' counter(pages) 即可输出 3 / 12 这样的页码格式,页码随分页自动递增,无需任何 JavaScript 参与。

页码格式可以定制:content: '第 ' counter(page) ' 页' 输出中文页码;counter(page, upper-roman) 输出罗马数字,适用于前言、目录等特殊章节;不同 @page 规则可以配合命名页使用不同的页码样式,实现正文与附件的页码分离。

计数器在 dompdf.js 中按渲染顺序自动维护,多页文档页码连续、总页数准确;需要注意计数器只在 @page 边距盒中直接可用,正文内容中无法直接读取页面计数,页眉页脚相关的动态信息统一放在边距盒中是最规范的做法。

页码样式与文档性质相关:正式报告用阿拉伯数字即可,前言目录可以用罗马数字,附录可以重新编号;通过命名页面与不同的 @page 规则组合,可以实现章节级页码控制,满足复杂文档的排版要求。

counter(pages) 依赖整个文档的页数统计,多次 addPage 时总页数自动累计;注意页码显示的是物理页数,与内容逻辑编号(如章节号)不同,需要逻辑编号时可以在模板中手动维护编号变量,灵活度更高。

代码示例:完整页眉页脚与页码

import { DomPDF } from 'dompdf.js';

const html = `
  <style>
    @page {
      size: A4;
      margin: 25mm 20mm 22mm 20mm;
      @top-center {
        content: '2026 年度经营分析报告';
        font-size: 10px;
        color: #666;
        border-bottom: 1px solid #ccc;
        padding-bottom: 4px;
      }
      @bottom-left {
        content: '云杉科技 · 机密文件';
        font-size: 9px;
        color: #999;
      }
      @bottom-right {
        content: '第 ' counter(page) ' 页 / 共 ' counter(pages) ' 页';
        font-size: 9px;
        color: #999;
      }
    }
    h1 { text-align: center; }
    .section { page-break-before: always; }
  </style>
  <h1>经营分析报告</h1>
  <p>第一页:摘要与关键指标……</p>
  <div class='section'>
    <h2>第二章 收入结构</h2>
    <p>第二页起的内容,由 page-break-before 强制分页……</p>
  </div>`;

const pdf = new DomPDF({ format: 'A4', margin: '20mm' });
pdf.addPage(html, { format: 'A4' });
pdf.save('page-media-demo.pdf');

分页控制:page-break 与孤儿寡行

分页控制属性决定内容在页面间的断点:page-break-before: always 强制元素从新页开始,适合章节标题;page-break-after: always 强制元素后换页;page-break-inside: avoid 防止元素内部被分页截断,适合卡片、表格行等完整单元。

孤儿寡行控制保证段落排版的完整性:orphans 指定段落分页时底部至少保留的行数,widows 指定顶部至少保留的行数,默认都是 2;正文段落设置 orphans: 3; widows: 3 可以避免页面边缘出现孤零零的一行文字,排版更专业。

分页控制的优先级与作用域需要理解:break-inside: avoid 作用于元素自身,page-break-before 作用于元素与其前文之间;嵌套结构中外层设置避免截断时,内层较长的元素可能被迫整体移到下一页,产生大面积空白,长内容需要分层设置,权衡完整性与空间利用。

分页控制的优先级规则值得单独记忆:break-inside: avoid 作用于元素内部,break-before 与 break-after 作用于元素边界;当内外层规则冲突时,外层容器保持完整的优先级更高,理解这一点可以准确预判长内容的断页位置。

避免分页时的常见误区:为追求每页整齐而给每个区块都加 page-break-before,会导致大量空白页;建议只对章节标题与关键区块强制分页,其余内容让引擎自然断页,配合孤儿寡行控制即可获得整齐的排版。

常见问题与调试技巧

Q: 页眉页脚没有显示?A: 检查 @page 规则是否写在样式表内且语法完整,边距盒的 content 属性是否赋值;注意 @page 内的嵌套规则(如 @top-center)必须位于 @page 大括号内部,位置写错会导致整条规则失效。

Q: 页码显示为 0 或没有递增?A: 检查 counter 函数的拼写与引号包裹,content: counter(page) 中 page 是关键字不能加引号;多页文档请确认 addPage 是按顺序添加内容,页码按渲染顺序递增,乱序添加会导致页码错乱。

Q: 边距设置后内容溢出到页边距?A: 检查边距盒内容是否过长,页眉文字超宽时会被截断;适当减小页眉字号或缩短文案,也可以让页眉只显示标题不显示副标题,保持边距盒整洁。

Q: 强制分页后出现空白页?A: 检查是否同时使用了 page-break-after: always 与下一个元素的 page-break-before: always,两个强制分页叠加会产生空白页;去掉其一,或改用 break-before 系列属性统一管理分页行为。

调试分页问题还有一个技巧:临时给每个区块加不同颜色的背景边框,渲染后观察断页位置与元素归属,能快速发现哪些元素被意外拆分;定位后再移除调试样式,正式模板保持干净。

如果页眉页脚与正文边距冲突(比如页眉压住正文),检查 margin 是否足够容纳页眉高度:页眉内容的高度会占用对应边距空间,边距过小会导致重叠,适当增加边距即可解决,输出更整洁。

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

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

Hello from dompdf.js!

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