react-pdf 사용법

샘샘·2024년 9월 3일
1

TypeScript

목록 보기
10/13

pdf 뷰어인 react-pdf 사용법
react-pdf 깃허브

1️⃣ install

pnpm add react-pdf
pnpm add core-js
react-pdf를 설치하고 withResolver가 없다는 에러를 막기 위해 core-js도 설치해준다

2️⃣ import

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에서 어떻게 보여줄지 커스텀 가능!

3️⃣ component

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} 설정을 해주었다

profile
회계팀 출신 FE개발자 👉콘테크 회사에서 웹개발을 하고 있습니다

0개의 댓글