- 견적 결과지 화면을 PDF로 생성해 서버에 저장하고, 이후 카카오톡으로 전송하는 기능을 구현했다.
- 화면에 보이는 UI를 그대로 PDF로 만들어야 했기 때문에
html2canvas-pro로 DOM을 이미지처럼 캡처하고, jsPDF를 이용해 PDF 파일로 변환하는 방식을 사용했다.
구현 흐름
- 결과지 영역 DOM 참조
- PDF 캡처용 스타일 적용
- html2canvas로 화면 캡처
- 캡처 이미지를 jsPDF에 추가
- PDF Blob을 File 객체로 변환
- 백엔드 저장 API 호출
npm install html2canvas-pro jspdf
const canvas1 = await html2canvas(contentRef.current, {
scale: 2,
useCORS: true,
backgroundColor: "#ffffff",
windowWidth: 1200,
});
const pdfBlob = pdf.output("blob");
const file = new File([pdfBlob], "estimate.pdf", {
type: "application/pdf",
});
캡처 영역 분리
- 각 영역을 별도로 캡처한 뒤 PDF의 1페이지, 2페이지로 추가했다.
const canvas1 = await html2canvas(contentRef.current, ...);
const canvas2 = await html2canvas(commentRef.current, ...);
PDF 캡처용 스타일 적용
contentRef.current.classList.add("pdf-capture-mode");
commentRef.current.classList.add("pdf-capture-mode");
.pdf-capture-mode,
.pdf-capture-mode * {
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
Arial, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif !important;
letter-spacing: 0px !important;
}
- 프로젝트의 기본 폰트는
Pretendard Variable이었다.
- 브라우저 화면에서는 문제가 없었지만,
html2canvas로 캡처할 때는 Variable Font가 기대한 것과 다르게 렌더링될 수 있었다.
- 특히 PDF 캡처 과정에서는 다음과 같은 문제가 발생할 수 있다.
- 글자 간격이 화면과 다르게 보임
- 일부 텍스트가 줄바꿈됨
- 한글 폰트가 깨지거나 두껍게 보임
- PDF 이미지 캡처 결과가 브라우저 화면과 달라짐
- 숫자의 line height가 이상해짐
- 그래서 PDF 캡처 시에는 Variable Font 대신 시스템 폰트 fallback을 사용하도록 변경했다.
letter-spacing:0px !important : 이는 캡처 시 글자 폭이 달라지면서 레이아웃이 밀리는 문제를 줄이기 위한 처리였다.
캡처 전 레이아웃 고정
- PDF는 화면 크기에 따라 레이아웃이 달라지면 안 된다.
- 그래서 캡처 직전에 컨테이너 너비를 고정했다.
containerRef.current.style.width = "1600px";
containerRef.current.style.maxWidth = "1600px";
containerRef.current.style.minWidth = "1600px";
containerRef.current.style.width = "";
containerRef.current.style.maxWidth = "";
containerRef.current.style.minWidth = "";
폰트 로딩 대기
- PDF를 생성하기 전에 폰트 로딩이 끝날 때까지 기다렸다.
await document.fonts.ready;
await new Promise((resolve) => setTimeout(resolve, 100));
- 폰트가 완전히 로드되기 전에 캡처하면 fallback 폰트로 먼저 캡처될 수 있기 때문에, 렌더링이 안정화된 뒤 캡처하도록 처리했다.
이미지 품질 설정
html2canvas 캡처 시 scale 값을 2로 설정했다.
scale: 2,
- scale이 낮으면 PDF에서 텍스트나 그래프가 흐릿하게 보일 수 있다.
- 그래서 캡처 해상도를 높여 PDF에서도 비교적 선명하게 보이도록 했다.
useCORS: true,
- 결과지에 외부 이미지가 포함될 가능성이 있어 true 옵션을 적용하였다.
- 이를 통해 CDN, S3 등 다른 도메인에서 제공되는 이미지도 html2canvas가 정상적으로 읽어 PDF에 포함할 수 있도록 구성하였다.
PDF 생성
- 캡처한 canvas는 이미지 데이터로 변환한 뒤 PDF에 추가했다.
const imgData1 = canvas1.toDataURL("image/jpeg", 1.0);
pdf.addImage(
imgData1,
"JPEG",
0,
0,
pageWidth,
calculatedHeight
);
- 첫 번째 영역을 추가한 뒤 새로운 페이지를 생성하고, 두 번째 캡처 이미지를 추가했다.
pdf.addPage([pageWidth, calculatedHeight], "p");
중복 저장 방지
- 저장 버튼을 여러 번 클릭하면 동일한 PDF가 중복 생성되거나 API가 중복 호출될 수 있다.
- 그래서
useRef를 이용해 저장 중 여부를 관리했다.
if (isSavingRef.current) return;
isSavingRef.current = true;
setIsSaving(true);
useState만 사용할 경우 상태 반영 타이밍 때문에 빠른 연속 클릭을 완전히 막기 어려울 수 있다.
- 그래서 즉시 값이 반영되는
useRef를 함께 사용했다.
네트워크 예외 처리
if (!navigator.onLine) {
setFailSaveNetworkModal(true);
return;
}
- 오프라인 상태에서는 PDF 생성 이후 서버 저장에 실패할 수 있기 때문에, 저장 전에 먼저 네트워크 상태를 먼저 확인한다.
마무리
- PDF 캡처용 폰트 적용
- letter-spacing 초기화
- 캡처 전 컨테이너 너비 고정
- 폰트 로딩 대기
- scale 설정으로 화질 개선
- 페이지 영역 분리
- 중복 저장 방지
- 등의 기능 적용