Paged.js 实战:浏览器端生成印刷级 PDF 的分页控制与避坑指南
简介这是一份面向前端开发者的 paged.js 系统教程资源聚焦如何用 JavaScript 将 HTML 内容转换为带优雅分页效果的 PDF 文档适合需要在浏览器端生成高质量打印或导出文档的开发者学习。压缩包共 32401 个文件约 40.16MB其中 28814 个 png 图片与 2918 个 js 脚本构成主要体量另含 otf、eot、woff、ttf 等字体文件用于排版渲染以及 html、svg、json、md 等示例与说明文件整体更像一套完整的文档站点或示例工程。教程内容覆盖 paged.js 的核心概念、npm 安装与引入、基础分页示例、页面尺寸与边距配置、自定义 page 与 media print 样式、CSS Grid 与 Flexbox 复杂布局处理、toBlob/toDataURL 输出 PDF、与 React/Vue/Angular 集成以及调试优化等环节。已有 585 人学习读者可借此掌握从网页到分页 PDF 的完整实现路径与排错思路。1. 从一份 Paged.js 教程拆起浏览器里跑出印刷级 PDF 的完整路径前端做报表导出十有八九被 PDF 折磨过。用window.print()吧分页全靠浏览器心情表头飘到第二页、页脚被裁掉是常态上服务端方案吧又得装一堆依赖调一次样式等一次构建。我最近拆了一份关于 Paged.js 的教程资源它给的思路很直接把 HTML 和 CSS 交给浏览器排版用一套 CSS Paged Media 规则控制分页、页眉页脚、页码最后导出成真正带分页语义的 PDF。这份教程适合谁适合已经会用 JavaScript 操作 DOM、被打印样式坑过、想在前端侧把「网页转印刷品」这件事做扎实的从业者。它不教你 CSS 基础但会把分页盒模型、页面上下文、断页控制这些平时用不到的知识点讲透。下面我按「这是什么 → 怎么跑起来 → 参数怎么调 → 坑在哪」的顺序把这份教程里能直接抄的部分整理出来。2. Paged.js 的运行机制分页盒模型与页面上下文2.1 它到底改写了什么普通网页是连续流浏览器只管滚动不管「页」的概念。Paged.js 做的事是在页面加载后接管 DOM把内容按你写的page规则切成一页一页的盒子再把这些盒子渲染成可视的分页预览。它实现的是 W3C 的 CSS Paged Media 规范里那部分浏览器一直没实现的能力page、page-break-*、break-*、页边距盒margin box、计数器页码。理解它的关键是分清两个坐标系。一个是文档流坐标系你的内容还在原来的 DOM 里另一个是页面坐标系Paged.js 把内容重新分配到一个个.pagedjs_page容器里。你写的page规则作用在后者上。教程里反复强调一句话不要用position: fixed去固定页眉那是屏幕思维在分页场景下会翻车正确做法是用top-center这类页边距盒。2.2 最小可运行示例教程给的最小示例很干净我把它整理成可以直接落地的版本。先建一个 HTML 文件引入 Paged.js然后写内容!DOCTYPE html html langzh head meta charsetUTF-8 title分页预览/title !-- 引入 Paged.js教程用的是 polyfill 版本 -- script srchttps://unpkg.com/pagedjs/dist/paged.polyfill.js/script style /* 页面尺寸与页边距A4 是印刷场景最常用的 */ page { size: A4; margin: 20mm 18mm 22mm 18mm; /* 页脚中间放页码counter(page) 是当前页 */ bottom-center { content: 第 counter(page) 页 / 共 counter(pages) 页; font-size: 10pt; color: #666; } /* 页眉左侧放文档标题 */ top-left { content: 内部技术文档; font-size: 9pt; color: #999; } } /* 正文基础排版 */ body { font-family: Songti SC, serif; font-size: 12pt; line-height: 1.7; } /* 一级标题后强制分页保证每章从新页开始 */ h1 { break-before: page; } /* 表格跨页时表头重复 */ thead { display: table-header-group; } /* 避免图片被拦腰截断 */ img { break-inside: avoid; } /style /head body h1第一章 概述/h1 p这里是正文内容Paged.js 会自动把它分配到页面盒子里。/p /body /html这段代码的逻辑说明page定义的是页面盒子size和margin决定纸张和版心bottom-center、top-left是页边距盒只有在这里写content才能生成页眉页脚写在普通元素上无效。counter(page)和counter(pages)是 Paged.js 注入的计数器前者是当前页后者是总页数。break-before: page是标准属性比老式的page-break-before更推荐Paged.js 两者都认。参数上最常改的是size除了A4还支持A5、letter也可以写size: 210mm 297mm自定义。margin四个值顺序是上右下左和 CSS 常规一致。页边距盒的位置关键字有top-left、top-center、top-right、bottom-left、bottom-center、bottom-right还有left-top这类侧边盒教程里主要用了上下六个。2.3 触发时机与异步问题Paged.js 默认在DOMContentLoaded后自动运行但如果你内容是异步渲染的比如 Vue、React 挂载完才出数据自动运行会拿到空内容。教程里给的解法是手动控制// 关闭自动运行改为手动触发 window.PagedConfig { auto: false, after: (flow) { console.log(分页完成共, flow.total, 页); } }; // 等你的数据渲染完再手动调用 async function renderPaged() { const paged new Paged.Previewer(); // 把当前文档内容交给 Paged.js 处理 await paged.preview(document.body.innerHTML, [], document.body); } // 假设数据请求完成后调用 fetch(/api/report).then(res res.json()).then(data { document.querySelector(#content).innerHTML buildHTML(data); renderPaged(); // 数据到位后再分页 });逻辑说明PagedConfig.auto false关掉自动执行避免抢跑。Previewer的preview方法接收三个参数——要处理的 HTML 字符串、样式表数组、挂载的目标容器。after回调里的flow.total能拿到总页数适合做「共 N 页」的二次处理。这里有个容易忽略的点preview是异步的必须await否则你拿到的 DOM 还是旧的。教程里特别提醒如果用了前端框架别让框架的虚拟 DOM 和 Paged.js 同时操作同一块节点常见做法是把内容渲染到一个独立容器再把这个容器的innerHTML交给 Paged.js。3. 分页控制实战断页、页眉页脚与页码计数器3.1 断页控制的三个层级分页质量好不好八成看断页控制。教程把断页分成三个层级来讲我按实操顺序梳理。第一层是块级断页控制「从哪里开始新页」。break-before: page用在章节标题上保证每章另起一页break-after: page用在某个总结段落后面。这两个属性比page-break-before/after更标准但为了兼容老浏览器教程建议两个都写h1 { break-before: page; page-break-before: always; /* 兼容写法 */ }第二层是行内断页控制「不要从哪里断开」。break-inside: avoid用在图片、表格行、代码块上防止它们被页面边界切成两半。这个属性对img、pre、tr特别有用。教程里有个细节break-inside: avoid对太高的元素无效如果一个表格比整页还高浏览器只能强行断开这时候要配合thead的display: table-header-group让表头在每页重复。第三层是孤行寡行控制orphans和widows。orphans控制页面底部最少留几行widows控制页面顶部最少留几行默认值都是 2。教程建议正文段落设成orphans: 3; widows: 3;避免一个段落只剩一行挂在页尾或页首这是印刷排版的基本要求但很多前端不知道。p { orphans: 3; /* 页尾至少 3 行 */ widows: 3; /* 页首至少 3 行 */ }3.2 页眉页脚的动态内容页眉页脚不只是放静态文字教程演示了几个动态场景。第一个是「当前章节标题显示在页眉」这需要用到string-set/* 把 h1 的文本内容存进一个叫 chapter 的字符串变量 */ h1 { string-set: chapter content(); } /* 在页眉里引用这个变量 */ page { top-right { content: string(chapter); font-size: 9pt; } }逻辑说明string-set: chapter content()把标题的文本抓取到名为chapter的字符串里content()表示取元素内容。页眉里用string(chapter)引用。这样每页页眉会自动显示当前页所属章节的标题翻页时自动更新。教程提醒string-set抓取的是「最后一个进入该页的 h1」如果一页里有多个 h1行为可能不符合预期常见做法是一页只放一个章节标题。第二个场景是「首页不显示页眉页脚」用:first伪类page :first { top-left { content: none; } bottom-center { content: none; } }第三个场景是「奇偶页页眉对称」用:left和:rightpage :left { top-left { content: 内部文档; } } page :right { top-right { content: 内部文档; } }这三个伪类:first、:left、:right是 Paged.js 支持的页面选择器教程里把它们归为「页面上下文」是控制页眉页脚位置的核心工具。3.3 页码计数器的进阶用法基础页码用counter(page)就够了但教程给了两个进阶玩法。第一个是「从指定页开始编号」比如封面和目录不编号正文从第 1 页开始/* 封面页重置计数器 */ .cover { counter-reset: page 0; } /* 或者用命名页面正文页重新计数 */ page content { counter-reset: page 1; }第二个是「页码格式转换」比如要显示罗马数字或中文数字。CSS 的counter()支持第二个参数指定样式page :first { bottom-center { content: counter(page, lower-roman); /* i, ii, iii */ } }lower-roman是小写罗马数字upper-roman是大写decimal是默认阿拉伯数字。教程里提到中文数字一二三CSS 原生不支持得用 JavaScript 在after回调里手动替换或者用counter-style自定义但后者在 Paged.js 里的支持不稳定常见做法还是 JS 后处理。参数上要注意counter-reset会重置计数器写在哪个元素上就从哪个元素之后开始重新计数。如果写在.cover上封面之后的内容页码从 1 开始但封面本身不显示页码。这个逻辑和 Word 的「分节符」类似理解了这个页码控制就不会乱。4. 避坑与排查分页不生效、样式丢失、导出空白4.1 坑一分页规则完全不生效现象写了page和break-before但预览里内容还是连续一长条没有任何分页。原因最常见的是 Paged.js 没加载成功或者加载了但没触发。其次是page规则写在了style里但被其他样式覆盖或者size值写错导致页面盒子没建立。解决先打开控制台看有没有Paged.js相关的报错确认脚本 200。然后在 Elements 面板里找.pagedjs_page这个类如果找不到说明分页根本没跑。如果找到了但样式不对检查page是不是被media print包住了——Paged.js 处理的是屏幕预览不需要media print包住反而可能不生效。教程里给的自检顺序是脚本加载 →PagedConfig配置 →page规则 → 断页属性一层层排除。4.2 坑二页眉页脚内容不显示现象top-center里写了content但页面上看不到。原因页边距盒的content只在page规则内有效写在普通选择器里无效。另一个常见原因是margin设得太小页边距盒没有空间渲染内容被挤没了。解决确认content写在page { top-center { ... } }结构里。然后检查margin上下边距至少要留出页眉页脚的高度一般20mm以上比较稳妥。如果用了string-set确认string()引用的变量名和string-set定义的一致大小写敏感。教程里有个血泪经验content里拼接字符串时字符串和counter()之间要有空格第 counter(page) 页这样写写成第counter(page)页在某些版本会解析失败。4.3 坑三导出 PDF 后内容空白或截断现象预览正常但用浏览器打印成 PDF 后部分页面空白或者内容被裁掉。原因Paged.js 的预览是 DOM 渲染打印时浏览器会重新走一遍打印流程如果打印样式和预览样式不一致就会出问题。常见的是page的size和打印对话框里的纸张尺寸不匹配或者margin在打印时被浏览器默认边距覆盖。解决打印时在浏览器打印对话框里把「边距」设为「无」「背景图形」勾上纸张尺寸选和page size一致。教程建议在page里显式写size: A4不要依赖默认值。如果还是空白检查是不是有元素设了overflow: hidden把内容裁了分页场景下尽量别用overflow控制高度。4.4 坑四异步内容分页错乱现象数据是异步加载的分页跑在了数据渲染之前结果只分了空壳。原因PagedConfig.auto默认是truePaged.js 在DOMContentLoaded就跑了此时异步数据还没回来。解决按 2.3 节的做法设auto: false在数据渲染完成后手动调preview。注意preview是异步的要await。如果用了框架确保框架的渲染周期结束再调常见做法是放在nextTick或useEffect的依赖数组里。教程里还提醒手动调用时传入的 HTML 字符串要是最终态别传了模板还没替换占位符。4.5 坑五中文字体在 PDF 里变成方块现象预览时中文正常导出 PDF 后中文变成方框或乱码。原因PDF 导出时字体没有正确嵌入或者font-family指定的字体在打印环境里不可用。解决font-family里优先用系统自带的中文字体比如Songti SC、SimSun、Microsoft YaHei并加serif或sans-serif兜底。如果对字体有严格要求用font-face引入 web 字体但要注意字体文件的跨域和格式woff2兼容性最好。教程里提到打印 PDF 时浏览器会尝试嵌入字体如果字体文件太大或格式不支持可能嵌入失败这时候换系统字体是最稳的。5. 从预览到交付批量导出与样式隔离的收尾技巧把单页调通只是第一步真正交付时往往要批量生成、要保证样式不污染主站。教程最后给的两个技巧我觉得是这份资源里最值钱的部分。第一个是批量导出。如果你有几十份数据要生成 PDF别一份份手动点打印。常见做法是用Previewer的preview方法循环处理每次处理完把生成的.pagedjs_pages容器取出来交给浏览器的打印接口。但浏览器打印是阻塞的没法在 JS 里静默完成所以教程给的思路是「生成预览 → 用户确认 → 手动打印」或者上无头浏览器方案。如果只是前端侧可以做一个「批量预览」页面把所有文档的分页结果渲染到不同容器用户逐个打印。这里有个细节每次preview会往目标容器里追加内容循环时要清空容器或者每次new Previewer()避免上一次的分页结果残留。// 批量预览每份数据渲染到独立容器 async function batchPreview(docs) { for (const doc of docs) { const container document.createElement(div); container.className preview-item; document.body.appendChild(container); const paged new Paged.Previewer(); // 每次用新的 Previewer避免状态串扰 await paged.preview(doc.html, [], container); } }逻辑说明new Previewer()每次新建实例保证计数器、字符串变量这些状态不跨文档污染。preview的第三个参数是挂载点传不同的容器就互不干扰。教程提醒批量场景下内存占用会上升几十份以上建议分批处理处理完一批把不需要的容器移除。第二个是样式隔离。Paged.js 会把你的内容重新挂载如果主站有全局样式比如body { font-size: 14px }可能影响分页结果。教程的做法是把分页内容放在一个独立的容器里用 CSS 作用域限制/* 只作用于分页容器内部 */ .paged-preview { all: initial; /* 重置继承样式谨慎使用 */ } .paged-preview p { font-size: 12pt; line-height: 1.7; }all: initial会把元素的所有属性重置为初始值能隔断外部继承但也会重置掉你想要的样式所以教程建议只在容器根元素上用内部再逐条定义。更稳的做法是给分页容器一个高优先级的类名所有分页样式都加这个前缀避免和主站冲突。最后一个技巧是关于验证的。教程建议在after回调里做一次页数和内容校验比如检查总页数是否和预期一致检查每页的页眉文本是否正确。我自己的习惯是每次改完分页样式先导出三页以内的短文档验证断页和页眉再上长文档。从那以后我每次调page规则都强制先跑一遍最小示例确认页边距盒和计数器正常再往真实内容上套。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

深入解析 docker-ce 中 google.golang.org/genproto:Go 生成的 Protobuf 与 gRPC API 包

深入解析 docker-ce 中 google.golang.org/genproto:Go 生成的 Protobuf 与 gRPC API 包

容器运行时云原生 【免费下载链接】docker-ce :warning: This repository is deprecated and will be archived (Docker CE itself is NOT deprecated) see the https://github.com/docker/docker-ce/blob/master/README.md :warning: 项目地址: https://gitcode.co…

2026/10/14 21:40:34 阅读更多 →
Windows第三方应用程序漏洞全解析:从DLL劫持到权限提升的排查与修复

Windows第三方应用程序漏洞全解析:从DLL劫持到权限提升的排查与修复

先说一件我亲身处理过的事。凌晨两点,某单位的IT值班人员发现一台开发终端CPU占用异常,进程列表里多了一个名字很像系统服务的进程,路径却指向用户临时目录。顺着进程链一层层追下去,最后定位到一个很不起眼的第三方小工具——某款…

2026/10/14 21:40:34 阅读更多 →
动物运动会系统:用策略+工厂+观察者模式实践开闭原则

动物运动会系统:用策略+工厂+观察者模式实践开闭原则

简介:本资源是一套基于Java实现的《动物运动会》综合教学系统,面向软件工程专业学生、设计模式初学者及Java进阶开发者,旨在通过真实业务场景深度理解并实践31种经典设计模式。系统以动物参赛、大厅交互、赛事执行等完整流程为载体&#xff0…

2026/10/14 21:40:34 阅读更多 →

最新新闻

Claude账号防封实战指南:从环境搭建到使用节奏的全面解析

Claude账号防封实战指南:从环境搭建到使用节奏的全面解析

1. 从“防封”这个词说起:账号稳定性的真实含义很多人第一次看到“防封”这两个字,脑子里浮现的画面是某种神秘操作或者隐藏开关。但如果你真正在长期使用各类AI工具,尤其是像Claude这种对账号行为比较敏感的服务,就会明白所谓的“…

2026/10/14 22:30:56 阅读更多 →
最简单的出入库系统怎么选?仓库管理软件选型避坑指南

最简单的出入库系统怎么选?仓库管理软件选型避坑指南

仓库管理软件哪个好用?这恐怕是我被问次数最多的问题,没有之一。每次听到这个问题,我都会先反问一句:你说的“好用”,到底是想要什么效果?聊下去之后,绝大多数人其实并不是想要一个功能多么齐全…

2026/10/14 22:30:56 阅读更多 →
Mac 源码编译 OpenClaw 实战:从依赖管理到避坑指南

Mac 源码编译 OpenClaw 实战:从依赖管理到避坑指南

简介:这份源码包面向需要在 Mac 上部署 OpenClaw 的开发者与运维人员,提供从环境准备到配置落地的完整参考。内容围绕 Homebrew、Node.js、Git 等基础依赖的确认,以及通过 brew 安装 OpenClaw CLI、配置本地大模型与网关端口、模型 ID 等关键…

2026/10/14 22:30:56 阅读更多 →
工业AI落地实践:从边缘计算到视觉质检与预测性维护

工业AI落地实践:从边缘计算到视觉质检与预测性维护

工业AI这四个字,这两年被喊得很响,但说实话,真正蹲过产线、在机台边吃过灰、把一个模型从实验室搬到车间里还让它稳定跑上三个月的人,并没有想象中那么多。我入这行快十年,前几年做的是传统机器视觉和PLC集成&#xff…

2026/10/14 22:30:56 阅读更多 →
DSP28335步进电机控制方案:ePWM脉冲输出与梯形加减速详解

DSP28335步进电机控制方案:ePWM脉冲输出与梯形加减速详解

简介:这是一套基于TI TMS320F28335 DSP与Xilinx XC3S500E FPGA协同控制步进电机的完整工程参考,适合电机控制学习者、嵌入式软硬件开发人员,以及开展课程设计或毕业设计的学生使用。整个压缩包共248个文件,大小约25.13MB&#xff…

2026/10/14 22:30:56 阅读更多 →
SQL注入本质:输入未过滤、拼接未隔离、权限未收敛

SQL注入本质:输入未过滤、拼接未隔离、权限未收敛

1. 这不是“黑进网站”的速成课,而是你真正该懂的SQL注入本质很多人看到“SQL注入”四个字,第一反应是黑客电影里飞速滚动的代码、被攻破的数据库、管理员惊恐的脸——这种刻板印象害了不少人。我带过不少刚入门的安全学习者,他们花大量时间背…

2026/10/14 22:29:55 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →