← dompdf.js Studio

Mobile Safari Adaptation Guide: Generating PDFs Reliably on iOS

Mobile Safari is the most demanding environment for frontend PDF generation. iOS devices span a wide range of memory budgets, Safari's behavior around WebAssembly and font loading differs from the desktop in subtle ways, and users habitually operate on weak networks — any single factor can turn a working export into a failure. dompdf.js supports Safari 15 and later, which means iPhone and iPad on iOS 15 and above can run the full DOM-to-PDF pipeline, but delivering stability on mobile requires an extra round of deliberate adaptation that desktop work does not prepare you for. This guide covers the complete mobile adaptation playbook: the supported scope and the system-level constraints of iOS Safari, memory and performance management for large documents, a mobile-safe generation pattern with timeouts and cleanup, the iOS-specific behavior of fonts and Chinese text, the Blob download and system share experience users actually expect on iOS, and a real-device testing checklist. The advice here is deliberately conservative: on mobile, the winning strategy is to do less per task — smaller documents, preloaded resources, explicit user consent for heavy operations — because every optimization that desktop browsers absorb silently can become a hard failure on an iPhone. Where the desktop guide says you can, the mobile guide says you should verify first; the sections below show the verification steps that matter, from memory profiling to testing the low-end models your analytics say your users actually hold. Following the playbook keeps your PDF feature stable on iPhones and iPads instead of turning mobile into the leading source of user complaints, and each section focuses on decisions you can make today — what to await, what to release, what to tell the user — rather than abstractions, so it translates directly into code changes for your export flow.

What iOS Safari Supports and What iOS Restricts

dompdf.js runs fully on iOS Safari 15 and later, which covers every iPhone and iPad on iOS 15 and iPadOS 15 and above. iOS 15 is the capability watershed because earlier Safari versions had incomplete WebAssembly support and cannot guarantee correct rendering. If a meaningful share of your users still runs older iOS, evaluate the upgrade curve first, then decide whether to invest in mobile adaptation at all or keep a server-side fallback for old devices.

Several system behaviors shape the mobile experience. Safari may freeze background tabs, so a long generation that loses foreground focus can be interrupted mid-render. Low Power Mode throttles CPU frequency, making generation noticeably slower. Under memory pressure Safari can reclaim the page entirely, which looks like a white screen or an automatic reload — the user's in-flight generation is lost, so your flow needs a recoverable state rather than assuming the page survives.

File delivery is another system-level difference: triggering a download on iOS does not behave like the desktop. In many iOS versions a[download] opens a PDF preview or presents the share sheet instead of silently saving, and newer iOS versions handle Blob URLs with stricter permission checks. Design the export flow around this reality — after generation, present a panel with system share and save options — instead of assuming desktop download semantics, and test the actual behavior on the iOS versions your users run.

Audit your analytics before investing in mobile polish: if exports on mobile are a small fraction of total usage, a minimal viable path — generate, share, save — may be enough, while a mobile-heavy product justifies the full adaptation described here. Either way, the decisions should be explicit and documented, because the mobile behavior of PDF features is easy to get wrong and expensive to rediscover later.

Memory and Performance: Why Big Documents Crash on iPhones

Memory is the most fatal constraint on mobile: Safari's per-tab memory budget on an iPhone is far below desktop, and when long documents, large images, and complex styles coexist, WASM rendering can push the page straight into reclamation. The countermeasures work at three levels: cap the input size by splitting very long documents into smaller segments, optimize resources by compressing images before embedding and subsetting fonts, and limit concurrency by avoiding heavy rendering tasks during generation.

The other key to holding peak memory down is disciplined release: revoke Blob URLs immediately after generation, remove large DOM nodes that are no longer needed, and make sure repeated exports do not accumulate memory. Stress-test by generating several documents back to back on an iPhone and watching the memory curve; if the peak rises with every export, something is leaking, and the snapshot and Blob lifecycles are the first places to inspect.

For genuinely large documents, give the user an explicit choice rather than a silent death: surface progress, and when memory pressure is likely, offer to export chapter by chapter or route to server-side generation. The goal of mobile adaptation is not to make every document generatable on a phone; it is to make reasonable documents stable on a phone and to give oversized scenarios a dignified escape hatch. Users prefer a clear downgrade path over a white screen every time.

Instrument memory behavior early rather than discovering it in crash reports: use the Safari Web Inspector's memory timeline while generating documents of increasing size, and record the threshold where the page starts to struggle. That threshold becomes a product decision — the largest document the mobile flow will attempt — and the export UI can warn users before they cross it, which is far better than letting the system kill the page mid-task.

Code Example: A Mobile-Safe Generation Pattern

Mobile generation code must care about two things desktop code often ignores: waiting and timeouts. document.fonts.ready can remain pending for a long time on weak networks, so race it against a ceiling — after five seconds, proceed and accept font fallback rather than leaving the user staring at a spinner forever. Images deserve the same treatment: confirm that the critical images have decoded before addPage so the snapshot does not miss them.

Manage user expectations actively during generation. Tell users to keep the page in the foreground so the system does not freeze the tab mid-render, and update the progress message when the task runs long so users know it is still alive. Mobile users abandon silent, feedback-free operations quickly; progress and messaging are part of the feature on a phone, not decoration you can skip.

Resource cleanup deserves special emphasis on mobile. After each generation, revoke temporary Blob URLs and unload large image nodes, so memory stays flat across repeated exports. Combined with segmenting large documents, this keeps single-task peak memory under the system's safety line, and the success rate of exports on iPhones and iPads rises substantially — it is the highest-ROI optimization available in mobile adaptation.

The pattern also guards against a mobile-specific failure mode: the user locks the screen or switches apps during a long generation. Because the render runs in a Worker, some work may continue in the background, but the browser can suspend the tab at any moment. Keep the generation state recoverable — for example, remember the source HTML and regenerate on return rather than resuming a half-dead pipeline — and the worst case becomes a retry instead of data loss. On low-memory devices, also disable heavy parallel work such as live previews while the export runs, so the tab keeps its memory headroom for the render itself.

async function generatePdfOnMobile(html, { timeoutMs = 60000 } = {}) {
  // 1. Wait for fonts and images so the snapshot captures complete content
  await Promise.race([
    document.fonts.ready,
    new Promise((r) => setTimeout(r, 5000)), // don't wait forever on weak networks
  ]);

  // 2. Warn the user to keep the page in the foreground
  showToast('Generating, please keep this page open…');

  const pdf = new DomPDF();
  pdf.addPage(html, { format: 'A4' });
  pdf.save('invoice.pdf');

  // 3. Release heavy resources after generation to keep memory stable
  setTimeout(() => {
    document.querySelectorAll('img[data-export]').forEach((img) => img.remove());
  }, 1000);
}

Fonts and Chinese Text on iOS

iOS Safari has two mobile-specific font quirks. First, the system caches Web Fonts aggressively; after a font file update, Safari may keep serving the old glyphs, so when font issues appear, test in a private window or after clearing cache before suspecting your code. Second, on weak networks a timed-out font request is not automatically retried, and the page silently falls back to system fonts — which changes the PDF's typography — so verify availability with document.fonts.check rather than assuming readiness.

Chinese rendering on iOS is generally reliable: dompdf.js ships Source Han Sans SC built in, and ordinary Simplified Chinese documents work without extra configuration. But coverage for rare characters, traditional forms, and special symbols depends on the actual font file, and since iOS cannot install system fonts on demand, the PDF must rely on embedded glyphs. Templates should therefore use the built-in font or explicitly embed a complete custom font file; a missing glyph costs more on mobile because there is no local fallback to rescue it.

Font size hits mobile harder too: a ten-megabyte font file takes a long time over cellular networks and may time out entirely in weak coverage. Prefer subsetted fonts or the built-in font on mobile to minimize network dependency. If a custom font is unavoidable, prefetch and cache it, then pass the cached ArrayBuffer into generation so every export does not re-download the file — users' data allowances and patience both benefit.

For documents that mix Chinese with Latin text, keep the font strategy simple on mobile: use the built-in Source Han Sans SC for body text and reserve custom fonts for headings, then subset even those to the characters actually used. A full CJK font embedded into every PDF makes the file larger than necessary and slows the WASM pipeline; the subset keeps the export lean while preserving the visual hierarchy.

The Blob Download and System Share Experience

Triggering a download on iOS behaves differently than on desktop: a[download] may open a PDF preview or present the share sheet rather than silently saving a file. Design the export flow to embrace this: after generation completes, present an action panel with options like Share via System and Save to Files, handing the user the native capabilities instead of pretending download semantics are universal. iOS users are accustomed to the Share Sheet, and the flow feels native rather than broken.

When the product needs the PDF to reach other people or persist long-term, guide users to the Share Sheet: it supports saving to Files, sending via Messages or Mail, and any installed share extension, so you get storage and distribution for free. For persistence, Save to Files combined with iCloud Drive gives cross-device sync, and these are standard iOS capabilities — integrating them costs little and raises the experience ceiling substantially compared to a forced download.

Test download and share behavior across iOS versions, because the panels differ: iOS 15 and iOS 17 present the share experience differently, and some versions enforce stricter Blob URL permission checks. Run the full flow on real devices — generate, preview, share, save — and confirm all four paths work before shipping mobile support. Otherwise you risk the classic report of nothing happens when I tap, which is nearly impossible to diagnose from a screenshot.

Consider offering multiple output formats from the same generation result: the PDF for formal sharing, plus a lightweight preview or a text version for quick review. On mobile, users often want to glance at the result before committing to a save or send, and a cheap preview reduces the number of full exports — each of which costs memory and time — while keeping the flow responsive.

Real-Device Debugging and a Testing Checklist

Mobile problems rarely reproduce in desktop emulation, so real-device debugging is mandatory. Connect an iPhone to a Mac with a cable and use Safari's Develop menu to remotely inspect the page — console, network, and memory are all visible. Without a Mac, use the browser's device emulation for a first pass, but treat system-level behavior and memory as real-device-only concerns, and make sure low-memory models like the iPhone SE are in the rotation.

A practical testing checklist: one real device on iOS 15 and one on the latest iOS; a low-memory model such as the iPhone SE or an older device; a throttled network profile; documents with many images and very long documents; several back-to-back generations to check memory stability; and the behavior when the user switches away mid-generation and returns. Record the result and a screenshot for each item so the checklist becomes a regression suite the team runs before every release.

Finally, feed the lessons back into the team's knowledge base: which models run out of memory, which iOS versions present the share sheet differently, how long to wait for fonts on weak networks. These observations drive future decisions and let new team members ramp up without re-discovering the same traps. Mobile adaptation is not a one-time project — iOS updates annually, and keeping the real-device rotation and the checklist current is what keeps PDF generation stable on mobile over the long run.

Treat the checklist as living documentation: whenever a new iOS version or device generation ships, run the rotation once and update the notes. The cost is an afternoon per release cycle, and the payoff is that mobile regressions are caught while they are still cheap to fix, instead of surfacing as a spike in support tickets after the upgrade wave hits.

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

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

Hello from dompdf.js!

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