Haeminway haeminway
한국어
Back to Tech Notes
2 min read

Why Saving a Screen as an Image Freezes on Mobile

Capturing the DOM via SVG foreignObject freezes or returns a blank image on mobile Safari and the GAS iframe. Draw to canvas directly, and download via Web Share.

Capturing the whole DOM with SVG foreignObject is not reliable on mobile. On mobile Safari and inside the GAS iframe, the capture freezes or returns a blank (white) image. Code that worked on desktop dies on a field phone.

Why it matters

The core output of a field app is usually a checklist PDF or an image export. If that freezes on a phone, the job stops there. The user only feels “the save button does nothing”: the cause (a rendering-engine difference) is invisible.

The fix: draw to canvas directly

Don’t rely on DOM capture. Draw the elements you need straight onto a canvas. Branch the capture method per export target, and before heavy work add a progress indicator, a one-frame yield, and a timeout guard.

async function exportImage(drawFn) {
  showProgress(); // show progress within 1s
  await nextFrame(); // yield one paint (so it doesn't look frozen)

  const canvas = document.createElement("canvas");
  canvas.width = 1080;
  canvas.height = 1527; // A4 ratio
  const ctx = canvas.getContext("2d");
  drawFn(ctx); // direct drawing, not foreignObject

  const blob = await new Promise((res) => canvas.toBlob(res, "image/png"));
  await share(blob, "inspection.png");
  hideProgress();
}

function nextFrame() {
  return new Promise((r) => requestAnimationFrame(() => r()));
}

Download is a trap too

<a download> is only a filename hint and does not guarantee a download on mobile. Use the Web Share API first; on failure, open a new tab and tell the user to “long-press to save.”

async function share(blob, name) {
  const file = new File([blob], name, { type: blob.type });
  if (navigator.canShare?.({ files: [file] })) {
    return navigator.share({ files: [file] }); // only inside a click handler
  }
  window.open(URL.createObjectURL(blob), "_blank"); // fallback
}

navigator.share must be called inside a user click handler. Defer it behind async work and the browser blocks it.

Deeper

  • High-res A4 uses a lot of mobile memory. Cap scale, and for large outputs render page-by-page, then compose.
  • Photo rotation. iPhone portrait photos carry EXIF orientation; without explicit rotation when drawing to canvas, they end up sideways or flipped.

One line to keep: on mobile, don’t screenshot the DOM: draw to canvas and export via share.

Frequently asked questions

Why does SVG foreignObject DOM capture return a blank image on mobile Safari?
The SVG foreignObject approach is unreliable on mobile Safari and inside the GAS iframe. The capture either freezes or returns a white image due to differences in the browser rendering engine.
How should I reliably export a canvas image on mobile?
Draw the elements you need straight onto a canvas instead of capturing the DOM. Before heavy work, add a progress indicator, a one-frame yield via requestAnimationFrame, and a timeout guard so the screen doesn't appear frozen.
Does the a download attribute work for saving images on mobile?
No. The download attribute is only a filename hint and does not guarantee a download on mobile. Use the Web Share API first; if that fails, open a new tab and guide the user to long-press to save.