Haeminway haeminway
English
기술 노트로
3 분 분량

모바일에서 화면을 이미지로 저장할 때 멈추는 진짜 이유

SVG foreignObject로 DOM을 캡처하면 모바일 Safari·GAS iframe에서 멈추거나 빈 이미지가 나온다. 직접 캔버스에 그리고, 다운로드는 Web Share로 받아라.

SVG foreignObject로 DOM을 통째로 캡처하는 방식은 모바일에서 신뢰할 수 없다. 모바일 Safari와 GAS의 iframe 환경에서 캡처가 멈추거나 빈(흰) 이미지가 나온다. 데스크톱에서 잘 되던 코드가 현장 폰에서 죽는다.

왜 중요한가

현장 앱의 핵심 결과물은 보통 점검표 PDF나 이미지 출력이다. 그게 폰에서 멈추면 업무가 거기서 끝난다. 사용자는 “저장 버튼이 안 먹는다”고만 느끼고, 원인(브라우저 렌더링 엔진 차이)은 보이지 않는다.

정답: 직접 캔버스에 그린다

DOM 캡처에 의존하지 말고 필요한 요소를 canvas에 직접 그린다. export 대상별로 캡처 방식을 분기하고, 무거운 작업 전에는 진행 표시 + 한 프레임 양보 + 타임아웃 가드를 둔다.

async function exportImage(drawFn) {
  showProgress(); // 1초 안에 진행 표시
  await nextFrame(); // 페인트 한 번 양보 (멈춤처럼 보이지 않게)

  const canvas = document.createElement("canvas");
  canvas.width = 1080;
  canvas.height = 1527; // A4 비율
  const ctx = canvas.getContext("2d");
  drawFn(ctx); // 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()));
}

다운로드도 함정이다

<a download>파일명 힌트일 뿐, 모바일에서 다운로드를 보장하지 않는다. Web Share API를 먼저 쓰고, 실패하면 새 탭으로 열어 “길게 눌러 저장”을 안내한다.

async function share(blob, name) {
  const file = new File([blob], name, { type: blob.type });
  if (navigator.canShare?.({ files: [file] })) {
    return navigator.share({ files: [file] }); // 클릭 핸들러 안에서만
  }
  window.open(URL.createObjectURL(blob), "_blank"); // fallback
}

navigator.share는 사용자 클릭 핸들러 안에서 호출해야 한다. 비동기 작업 뒤로 미루면 브라우저가 막는다.

깊이

  • 고해상도 A4는 모바일 메모리를 많이 쓴다. scale을 제한하고, 큰 출력은 페이지 단위로 그린 뒤 합성한다.
  • 사진 회전. iPhone 세로 사진은 EXIF orientation이 있어, canvas에 그릴 때 명시적으로 회전 보정하지 않으면 눕거나 뒤집힌다.

핵심 한 줄: 모바일 export는 DOM을 찍지 말고, 캔버스에 그려서 공유로 내보내라.

자주 묻는 질문

모바일 Safari에서 SVG foreignObject로 DOM을 캡처하면 왜 빈 이미지가 나오나요?
SVG foreignObject 방식은 모바일 Safari와 GAS의 iframe 환경에서 신뢰할 수 없습니다. 캡처가 멈추거나 흰 이미지만 반환되는데, 브라우저 렌더링 엔진 차이 때문입니다.
모바일에서 이미지 export를 안정적으로 하려면 어떻게 해야 하나요?
DOM 캡처에 의존하지 말고 필요한 요소를 canvas에 직접 그려야 합니다. 무거운 작업 전에는 진행 표시, 한 프레임 양보(requestAnimationFrame), 타임아웃 가드를 함께 써야 멈춤처럼 보이지 않습니다.
모바일에서 canvas 이미지를 저장할 때 a download 태그를 쓰면 안 되나요?
a download는 파일명 힌트일 뿐, 모바일에서 다운로드를 보장하지 않습니다. Web Share API를 먼저 시도하고, 실패하면 새 탭으로 열어 길게 눌러 저장하도록 안내하는 방식이 권장됩니다.