웹, 안드로이드에서는 ok,
아이폰에서 캡쳐가 안되는 현상 발생.
아이폰 safari가 다른 운영체제보다 훨씬 까다로운 보안정책을 가지고 있기에
이 문제를 해결하기 위해 캡쳐에 사용중인 html2canvas의 작동방식부터 분석했다.
html2canvas의 작동방식
직관적으로, 화면을 캡쳐하는 것을 카메라로 찍어낸다고 생각하기 쉽다.
하지만 내부 작동방식은 그렇지 않다.
실제로는 화면의 DOM을 읽어서 캔버스에 다시 렌더링하는 과정을 거친다.
각 html태그의 위치/크기/텍스트/폰트 스타일 등을 수집하고 가상의 <canvas> 객체 위에 그림을 다시 그리듯이 재구성한다.
작업이 끝난 후 완성된 canvas객체를 우리에게 전달하고
우리는 이것을 다시 이미지파일로 바꾼다.
html2canvas 를 사용한 코드 일부분을 보면 이해가 쉬울 것이다.
html2canvas(document.body, {
x: rect.left,
y: rect.top,
width: rect.width,
backgroundColor: '#ffffff',
height: rect.height,
scrollX: 0,
scrollY: -window.scrollY,
useCORS: true,
scale: 1,
}).then(canvas => {
const uuid = generateUUID()
imageData = canvas.toDataURL('image/png')
const html = <img src='${imageData}' class="preview" crossOrigin="anonymous"></li>
$('.capture-div').before(html)
})
오류분석과정
html2canvas 옵션 중 logging이라는 옵션이 있다.
활성화하면 캡쳐를 하고 수집한 요소들을 콘솔에서 볼 수 있다.

또, ignoreElements 이라는 옵션이 있는데 캡쳐 범위에서 제외하는 요소를 선택할 수 있다.
이 두 옵션을 사용해서 수집되는 요소들을 보며 하나씩 원인을 찾아나갔다.
html2canvas(document.body, {
// ... x, y, width, height, useCORS 등 기존 옵션
logging : true,
ignoreElements: (element) => {
const name = element.tagName.toUpperCase();
if (name === 'BUTTON' || name === 'IMG') {
return true;
}
return false;
}
}).then(canvas => {
// ... Blob 및 FileReader 로직
});
그러다가 알게 된 사실 중 하나는, 캡쳐 범위와 상관 없이 요소를 스캔한다는 것이다.
위 html2canvas 코드에 첫 인자로 넘겨주는 document.body가 그 스캔 영역을 지정하는 변수였나 보다..
그리고 드디어 원인을 찾았다.
바로 .svg 확장자를 가진 이미지 파일이 문제였다.
그 이유를 알아본 바
svg는 그림이 아니라 xml기반의 코드이기에 내부에 <script>태그를 심을 수도 잇고 특정 악의적인 조작을 할 가능성이 있기 때문에,
까다로운 아이폰 safari가 CORS 미인증 SVG를 받아주지 않는 것이었다.
해결방법
할 수 있는 조치로 가장 빠른 방법은
- svg가 들어간 button/img를 모두 제외한다.
- svg를 모두 img로 바꾼다.
- img 태그에 url로 .svg 를 요청하지 말고 Base64로 문자열로 변환 후 박아버리는 것
.btn-has-svg::before {
content: url('../img/icon.svg');
}
.btn-has-svg::before {
content: url('data:image/svg+xml;base64,PHN2ZyB3...[엄청긴문자열]...');
}
정도가 있다..
다른 방법은 더 찾아봐야겠지만 나는 이정도로 충분했다.
'개발 > ⚽ 트러블슈팅' 카테고리의 다른 글
| DEXTUploadNJ 대용량 파일업로드 라이브러리 에러 원인 찾기 (0) | 2026.06.18 |
|---|