React:Examples:ExportPdfForHiddenComponents
숨겨놓은 React컴포넌트를 PDF 로 저장하는 예시.
미디어 쿼리 안쓰는 이유
@media print
는 브라우저 인쇄 기능(Ctrl+P)을 사용했을 때만 작동한다.
따라서 별도의 프린트 팝업 없이 바로 PDF 다운로드 하고싶다면 아래와 같이 진행해야 한다.
iframe 사용하지 않는 이유
- iframe은 완전히 독립된 문서예요.
- React 컴포넌트는 Virtual DOM 안에서 작동하기 때문에, 다른 DOM(DocumentContext)에 직접 삽입하려면 별도의 렌더링 방식이 필요해요.
- iframe은 CSS 격리가 되기 때문에, 앱의 전역 스타일이나 테마가 iframe에 적용되지 않아요.
- 따로 CSS를 로딩하거나 인라인으로 전달해줘야 함.
- props, context, 상태 등을 iframe으로 넘기기 위해선 postMessage, JSON serialization 등 추가적인 작업 필요.
- 간단하게 컴포넌트를 넘길 수 없음.
숨겨 놓은 HTML 요소 만들기
<div
ref={componentRef}
style={{
position: "fixed",
top: 0,
left: 0,
width: "210mm",
height: "297mm",
zIndex: -9999,
opacity: 0,
pointerEvents: "none",
overflow: "hidden",
}}
>
<OtherPageComponent />
</div>
스타일 속성 | 이유 |
| 스크롤과 무관하게 화면 고정 |
| 화면 제일 뒤로 (보이지 않게) |
| 눈에 안 보이게 |
| 클릭 등 이벤트 차단 |
| 혹시 내부 넘침 방지 |
| PDF 출력에 맞는 정확한 크기 |
Export PDF 아이콘 다운로드
https://icones.js.org/ 에서 적당한 아이콘 다운로드.
import {SVGProps} from 'react';
export function BiFileEarmarkPdf(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 16 16"
{...props}
>
<g fill="currentColor">
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2M9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"></path>
<path d="M4.603 14.087a.8.8 0 0 1-.438-.42c-.195-.388-.13-.776.08-1.102c.198-.307.526-.568.897-.787a7.7 7.7 0 0 1 1.482-.645a20 20 0 0 0 1.062-2.227a7.3 7.3 0 0 1-.43-1.295c-.086-.4-.119-.796-.046-1.136c.075-.354.274-.672.65-.823c.192-.077.4-.12.602-.077a.7.7 0 0 1 .477.365c.088.164.12.356.127.538c.007.188-.012.396-.047.614c-.084.51-.27 1.134-.52 1.794a11 11 0 0 0 .98 1.686a5.8 5.8 0 0 1 1.334.05c.364.066.734.195.96.465c.12.144.193.32.2.518c.007.192-.047.382-.138.563a1.04 1.04 0 0 1-.354.416a.86.86 0 0 1-.51.138c-.331-.014-.654-.196-.933-.417a5.7 5.7 0 0 1-.911-.95a11.7 11.7 0 0 0-1.997.406a11.3 11.3 0 0 1-1.02 1.51c-.292.35-.609.656-.927.787a.8.8 0 0 1-.58.029m1.379-1.901q-.25.115-.459.238c-.328.194-.541.383-.647.547c-.094.145-.096.25-.04.361q.016.032.026.044l.035-.012c.137-.056.355-.235.635-.572a8 8 0 0 0 .45-.606m1.64-1.33a13 13 0 0 1 1.01-.193a12 12 0 0 1-.51-.858a21 21 0 0 1-.5 1.05zm2.446.45q.226.245.435.41c.24.19.407.253.498.256a.1.1 0 0 0 .07-.015a.3.3 0 0 0 .094-.125a.44.44 0 0 0 .059-.2a.1.1 0 0 0-.026-.063c-.052-.062-.2-.152-.518-.209a4 4 0 0 0-.612-.053zM8.078 7.8a7 7 0 0 0 .2-.828q.046-.282.038-.465a.6.6 0 0 0-.032-.198a.5.5 0 0 0-.145.04c-.087.035-.158.106-.196.283c-.04.192-.03.469.046.822q.036.167.09.346z"></path>
</g>
</svg>
);
}
export default BiFileEarmarkPdf;
툴팁이 적용된 아이콘 버튼 적용:
<div
className="tw-group tw-relative tw-flex tw-justify-center tw-items-center"
onClick={e => onClickExportPdf(e, d)}
>
<BiFileEarmarkPdf className="tw-text-center tw-text-lg tw-m-1" />
<span className="group-hover:tw-opacity-70 tw-transition-opacity tw-bg-gray-500 tw-py-1 tw-px-2 tw-text-xs tw-text-gray-100 tw-rounded-md tw-absolute tw-left-1/2 -tw-translate-x-1/2 tw-translate-y-full tw-opacity-0 tw-m-4 tw-mx-auto">
PDF
</span>
</div>
적당한 위치에 버튼과 이벤트 핸들러 추가
import BiFileEarmarkPdf from '../icons/BiFileEarmarkPdf';
import styles from './DailyWorkStatus.module.scss';
// ...
export default function DailyWorkStatus() {
// ...
const pdfLayoutRef = useRef<HTMLDivElement>(null);
const onClickExportPdf = (
e: React.MouseEvent<SVGSVGElement, MouseEvent>,
data: DailyInfoData
) => {
e.stopPropagation();
};
// ...
<BiFileEarmarkPdf onClick={e => onClickExportPdf(e, d)} />
// ...
<div ref={pdfLayoutRef} className={styles.pdfLayout}></div>
// ...
}
See also
- React
- react-pdf - React renderer for creating PDF files on the browser and server.
- react-to-pdf
- html2canvas - HTML 랜더링 결과를 Canvas 로 복사.
- jsPDF