pdf 뷰어인 react-pdf 사용법
react-pdf 깃허브
pnpm add react-pdf
pnpm add core-js
react-pdf
를 설치하고 withResolver가 없다는 에러를 막기 위해 core-js
도 설치해준다
import { Document, Page, pdfjs } from "react-pdf";
import "core-js/full/promise/with-resolvers.js";
import "pdfjs-dist/webpack";
// Polyfill for environments where window is not available (e.g., server-side rendering)
// @ts-expect-error This does not exist outside of polyfill which this is doing
if (typeof Promise.withResolvers === "undefined") {
if (typeof window !== "undefined") {
// @ts-expect-error This does not exist outside of polyfill which this is doing
window.Promise.withResolvers = function () {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
} else {
// @ts-expect-error This does not exist outside of polyfill which this is doing
global.Promise.withResolvers = function () {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
}
}
// ❌❌❌❌ 아래 코드를 사용하게 되면 build 에러 발생
// import "pdfjs-dist/webpack"; 해주고
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/legacy/build/pdf.worker.min.mjs",
import.meta.url,
).toString();
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
// 위 코드로 대신 사용!
pdf를 보여주는 컴포넌트 상단에 위의 코드를 import 한다
Document
: prop를 사용하여 전달된 문서를 로드한다
Page
: 페이지를 표시하며 내부에 배치해야 한다
Document에서 파일을 로드 👉 Page에서 어떻게 보여줄지 커스텀 가능!
import { useState } from 'react';
import { Document, Page } from 'react-pdf';
function MyApp() {
const [numPages, setNumPages] = useState<number>();
const [pageNumber, setPageNumber] = useState<number>(1);
function onDocumentLoadSuccess({ numPages }: { numPages: number }): void {
setNumPages(numPages);
}
return (
<div>
<Document file="somefile.pdf" onLoadSuccess={onDocumentLoadSuccess}>
<Page pageNumber={pageNumber} />
</Document>
<p>
Page {pageNumber} of {numPages}
</p>
</div>
);
}
공식에서 소개하고 있는 기본 사용법이다
numPages
는 전체 페이지 수이고 pageNumber
는 현재 페이지를 나타낸다
onLoadSuccess
이벤트를 사용해서 로드가 되면 페이지를 할당해주는 함수를 만들어 사용한다
⭐️ Document
컴포넌트의 file
에는 띄워줄 pdf의 url을 넘겨줘야 한다 ⭐️
나는 디자인대로 만들기 위해 약간의 커스텀을 했다
import React from "react";
import { Document, Page, pdfjs } from "react-pdf";
import "core-js/full/promise/with-resolvers.js";
import * as S from "./style";
import dynamic from "next/dynamic";
import { colors } from "@builderhub/mui-theme";
import DialogXIcon from "components/Icons/AddDelete/DialogXIcon";
import { LeftArrowCircle, PdfDownload, RightArrowCircle } from "components";
import { FloorNameList } from "../Provider/types";
import { PdfLoading } from "./Loading";
const ScrollArea = dynamic(() => import("react-scrollbar"), {
ssr: false,
});
// Polyfill for environments where window is not available (e.g., server-side rendering)
// @ts-expect-error This does not exist outside of polyfill which this is doing
if (typeof Promise.withResolvers === "undefined") {
if (typeof window !== "undefined") {
// @ts-expect-error This does not exist outside of polyfill which this is doing
window.Promise.withResolvers = function () {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
} else {
// @ts-expect-error This does not exist outside of polyfill which this is doing
global.Promise.withResolvers = function () {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
}
}
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/legacy/build/pdf.worker.min.mjs",
import.meta.url,
).toString();
interface PropType {
pdfOpen: boolean;
handleViewerClose: () => void;
pdfData: FloorNameList;
}
export const PdfViewer = (props: PropType) => {
const { pdfOpen, handleViewerClose, pdfData } = props;
const [isScroll, setIsScroll] = React.useState(false);
const [numPages, setNumPages] = React.useState(0);
const [pageNumber, setPageNumber] = React.useState(1);
const [showPage, setShowPage] = React.useState(false);
const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
setNumPages(numPages);
setPageNumber(1);
};
const downloadPdf = (url: string) => {
if (url) {
window.open(url);
}
};
return (
<S.DialogWrap open={pdfOpen}>
<S.Wrap
onMouseEnter={() => setShowPage(true)}
onMouseLeave={() => setShowPage(false)}
>
<S.TabHeader>
<S.HeaderBtn
onClick={() =>
downloadPdf((pdfData[0] && pdfData[0].signedUrl) as string)
}
>
<PdfDownload />
</S.HeaderBtn>
<S.HeaderBtn onClick={handleViewerClose}>
<DialogXIcon />
</S.HeaderBtn>
</S.TabHeader>
<ScrollArea
horizontal={false}
stopScrollPropagation={true}
verticalContainerStyle={{
width: 16,
background: "none",
}}
verticalScrollbarStyle={{
width: 4,
background: colors.gray._20,
borderRadius: 2,
opacity: isScroll ? 1 : 0,
}}
style={{
maxHeight: 640,
width: "100%",
}}
>
<Document
onMouseEnter={() => {
setIsScroll(true);
}}
onMouseLeave={() => {
setIsScroll(false);
}}
file={pdfData[0] && pdfData[0].signedUrl}
onLoadSuccess={onDocumentLoadSuccess}
loading={<PdfLoading />}
>
<Page
height={640}
width={1120}
pageNumber={pageNumber}
renderTextLayer={false}
renderAnnotationLayer={false}
loading={<PdfLoading />}
/>
</Document>
</ScrollArea>
{showPage && numPages > 0 && (
<>
{pageNumber === 1 ? null : (
<S.MovePage
direction="left"
onClick={() =>
pageNumber > 1 ? setPageNumber(pageNumber - 1) : null
}
>
<LeftArrowCircle />
</S.MovePage>
)}
{numPages === pageNumber ? null : (
<S.MovePage
direction="right"
onClick={() =>
pageNumber < numPages ? setPageNumber(pageNumber + 1) : null
}
>
<RightArrowCircle />
</S.MovePage>
)}
<S.PageNumber>
{pageNumber} / {numPages}
</S.PageNumber>
</>
)}
</S.Wrap>
</S.DialogWrap>
);
};
Dialog
를 사용해서 모달에 pdf 뷰어를 띄워주었다
그리고 마우스를 호버하면 스크롤과 페이지들이 나왔다가 사라지는 것과
loading 속성도 사용해서 파일이 로딩 중일 때의 UI도 나타내주었다
pdf 파일 내의 주석과 텍스트 레이어를 보여줄 수 있는 설정도 있는데
나는 오직 pdf만 보여주기 위해 Page
컴포넌트 내부에 renderTextLayer={false} renderAnnotationLayer={false}
설정을 해주었다