移动端 Safari 是前端 PDF 生成最挑剔的环境:iOS 设备型号跨度大、内存差异悬殊、Safari 对 WASM 与字体加载的行为和桌面端并不完全一致,用户还习惯在弱网下操作,任何一个因素都可能让生成失败。dompdf.js 支持 Safari 15 及以上版本,在 iOS 15 之后的 iPhone 与 iPad 上可以正常完成 DOM 到 PDF 的转换,但要在移动端做到稳定,需要比桌面端多做一轮针对性适配。本文围绕移动端 Safari 的完整适配方案展开:先梳理 iOS 上的支持范围与系统限制,再重点解决内存与性能问题,给出适配后的安全生成代码示例,随后讲解字体与中文渲染在 iOS 的差异、Blob 下载与系统分享的体验设计,最后提供真机调试与测试清单。按这份指南适配,你的 PDF 生成功能在 iPhone 和 iPad 上也能保持桌面端的稳定体验,而不是让移动端成为用户投诉的重灾区。
dompdf.js 在 iOS Safari 15 及以上版本可完整运行,对应的系统是 iOS 15 与 iPadOS 15 之后的所有版本,覆盖 iPhone 与 iPad 全系列。iOS 15 是能力分水岭,此前的 Safari 对 WebAssembly 的支持不完整,无法保证渲染正确。如果你的用户群仍有大量旧系统设备,需要先评估升级比例,再决定是否投入移动端适配,或为旧系统保留服务端生成兜底。
iOS Safari 的一些系统级行为会影响生成:后台标签页可能被系统冻结,长时间生成的页面切到后台再回来,渲染可能已中断;低电量模式会限制 CPU 频率,生成耗时明显变长;内存压力大时 Safari 会直接回收页面,表现为页面白屏或自动刷新,用户正在进行的生成操作会丢失,需要设计可恢复的状态。
另一个系统限制是文件下载体验:iOS 上 a[download] 标签的触发方式与桌面端不同,早期版本会直接打开 PDF 预览而不是保存,iOS 15 之后多数场景会进入分享面板。导出流程要主动适配这种交互,比如生成完成后弹出操作面板,让用户选择用系统分享保存到文件 App,而不是假设下载行为与桌面端一致。
移动端最致命的问题是内存:iPhone 的 Safari 对单个标签页的内存预算远低于桌面端,长文档、大图片、复杂样式同时存在时,WASM 渲染可能直接触发页面回收。对策分三个层面:控制输入规模,把超长文档拆成多个小文档分段生成;优化资源,图片先压缩再嵌入,字体按需子集化;限制并发,避免生成过程中同时渲染其他重任务。
降低内存峰值的另一个关键是及时释放资源:生成完成后立即 revoke 掉 Blob URL,不再使用的 DOM 节点及时移除,避免重复生成时内存持续累积。在 iPhone 上可以连续生成多份文档做压测,观察内存曲线是否稳定,如果峰值随生成次数上升,说明存在泄漏,优先排查快照与 Blob 的生命周期管理。
对大文档场景,建议给用户提供明确的进度反馈,并在检测到内存压力时主动降级:提示用户关闭后台应用、分章节导出,或改用服务端生成。与其让用户面对白屏,不如把选择权交给用户。移动端适配的目标不是让所有文档都能在手机上生成,而是让合理的文档在手机上稳定生成,超限场景给出体面的降级路径。
移动端生成代码要额外关注两件事:等待与超时。document.fonts.ready 在弱网下可能长时间不 resolve,用 Promise.race 给它一个上限,超时后继续生成并接受字体回退,比让用户无限等待更合理。图片同理,在调用 addPage 前确认关键图片已解码完成,快照才不会缺图。
生成过程要主动管理用户预期:提示用户保持页面在前台,避免切后台导致渲染被系统冻结;耗时超过预期时更新提示文案,让用户知道任务仍在进行。移动端用户没有耐心等待无反馈的任务,进度与提示的设计直接决定功能在手机上的可用性,而不是可有可无的锦上添花。
资源释放要在移动端特别强调:每次生成后清理临时的 Blob URL、卸载不再需要的大图节点,让重复生成的场景内存保持稳定。配合分段生成策略,把单次任务的内存峰值控制在系统安全线以内,iPhone 与 iPad 上的生成成功率会显著提升,这是移动端适配投入产出比最高的优化。
async function generatePdfOnMobile(html, { timeoutMs = 60000 } = {}) {
// 1. 先等字体与图片就绪,避免快照时内容缺失
await Promise.race([
document.fonts.ready,
new Promise((r) => setTimeout(r, 5000)), // 弱网兜底,不无限等待
]);
// 2. 生成前提示用户保持前台,避免系统回收
showToast('生成中,请保持页面在前台…');
const pdf = new DomPDF();
pdf.addPage(html, { format: 'A4' });
pdf.save('invoice.pdf');
// 3. 生成后主动释放,避免内存累积
setTimeout(() => {
document.querySelectorAll('img[data-export]').forEach((img) => img.remove());
}, 1000);
}
iOS Safari 的字体加载有两个移动端特有的坑:一是系统对 Web Font 的缓存策略更激进,字体文件更新后可能继续使用旧缓存,排查字体问题时先清缓存或换无痕模式验证;二是弱网下字体请求超时后不会自动重试,页面静默使用回退字体,PDF 里的字形随之变化,验证字体是否可用要用 document.fonts.check。
中文渲染在 iOS 上整体可靠,dompdf.js 内置思源黑体,常见的简体中文文档可以直接使用;但生僻字、繁体字与特殊符号的覆盖范围取决于具体字体文件,移动端无法安装系统字体,PDF 里必须依赖嵌入的字形,因此模板里的中文字体要么用内置字体,要么显式嵌入完整的自定义字体文件,缺字的代价在移动端更高。
字体体积在移动端影响更直接:十几兆的字体文件在 4G 网络下要等很久,弱网下还可能超时失败。建议移动端使用子集化字体或直接依赖内置字体,把网络依赖降到最低;如果必须使用自定义字体,提前预取并缓存,生成时用缓存的 ArrayBuffer 传入,避免每次导出都重新下载,用户的流量与等待时间都能省下来。
iOS 上触发下载后,Safari 的行为与桌面端不同:a[download] 在 iOS 上可能直接打开 PDF 预览页,或弹出分享面板,而不是静默保存到本地。设计导出流程时要主动适配:生成完成后弹出包含用系统分享保存、复制链接等选项的操作面板,把系统能力直接暴露给用户,体验比强制下载更符合 iOS 用户习惯。
如果产品需要把 PDF 发给他人或保存到文件 App,可以在操作面板里引导用户使用系统分享面板,iOS 的 Share Sheet 天然支持存储到文件、发送到微信邮件等路径,无需自己实现。对需要持久化的场景,也可以引导用户开启在文件 App 中打开,配合 iCloud Drive 实现跨设备同步,这些都是 iOS 生态的标准能力,接入成本低、体验上限高。
注意测试不同 iOS 版本下下载与分享行为的一致性:iOS 15 与 iOS 17 的分享面板表现就有差异,部分系统版本对 Blob URL 的权限校验更严格。真机上把导出流程完整走一遍,确认生成、预览、分享、保存四条路径都通畅,再上线移动端功能,避免用户反馈点了没反应却查不出代码问题的尴尬。
移动端问题很难在桌面模拟器里完全复现,真机调试是必备手段:iPhone 通过数据线连接 Mac,在 Safari 的开发菜单里可以远程调试页面,查看 console、网络与内存信息。没有 Mac 时,可以用浏览器的设备模拟模式做初步验证,但内存与系统级行为仍以真机为准,尤其要覆盖 iPhone SE 这类低内存机型。
测试清单建议覆盖:iOS 15 与最新版 Safari 各一台真机、低内存机型(iPhone SE 或旧款)、弱网环境(开发者工具限速)、大文档与多图文档、连续多次生成的内存稳定性、生成中切后台再回来的行为。每项都记录结果与截图,形成一份移动端回归清单,每次发版前过一遍,比临时抱佛脚可靠得多。
最后,把移动端适配的经验沉淀进团队知识库:哪些机型内存吃紧、哪些 iOS 版本分享行为不同、弱网下字体等待多久超时合适。这些数据是后续迭代的决策依据,也是新成员快速上手的基础。移动端适配不是一次性工作,iOS 每年更新一次,保持真机测试与清单更新的节奏,才能让 PDF 生成在移动端长期稳定。
下面的按钮用 dompdf.js 在浏览器端实时生成 PDF,无需后端:
这是由 dompdf.js 渲染的示例 PDF 内容。