Daily Front_Minhhk

[next.js] react-pdf__적용 (+ zoom in/out) 본문

Code개발일지

[next.js] react-pdf__적용 (+ zoom in/out)

Minhhk 2024. 5. 14. 16:19
 

react-pdf

Display PDFs in your React app as easily as if they were images.. Latest version: 8.0.2, last published: 7 days ago. Start using react-pdf in your project by running `npm i react-pdf`. There are 784 other projects in the npm registry using react-pdf.

www.npmjs.com

 

pdf 뷰어 다시 적용할 작업이 생겨!

오랜만에 리마인드 느낌으로 다시 작성@

 

 

next.config.mjs 파일 수정!!
const nextConfig = {
  //? react-pdf__config 설정____________________
  webpack: config => {
    config.resolve.alias.canvas = false;

    return config;
  },
 };

 

 

뷰어 전체 컴포넌트와 pdf컴포넌트 따로 작성한다.

 

pdf 파일 데이터를 받아 base64Decoding 한다.

변환 코드는 util 에 넣어 사용!

export const base64Decoding = (base64String: string) => {
  if (!base64String) return "";

  const decodedData = atob(base64String);
  const byteArray = new Uint8Array(decodedData.length);
  for (let i = 0; i < decodedData.length; i++) {
    byteArray[i] = decodedData.charCodeAt(i);
  }
  const blob = new Blob([byteArray], { type: "application/pdf" });
  return URL.createObjectURL(blob);
};

 

 

Zoon In / Out

 

  • scale state 를 만들어서,, pdfViewer 의 scale 옵션값에 넣어 줄 것이다.
  • 아래는 관련 함수들
  const [scale, setScale] = useState<number>(1);

  // 최소는 0.2 , 최대는 2.4  까지
  const handlePdfZoomOut = () => {
    setScale(prevScale => Math.max(prevScale - 0.2, 0.2));
  };

  const handlePdfZoomIn = () => {
    setScale(prevScale => Math.min(prevScale + 0.2, 2.4));
  };

 

 

이제 연결할 Viewer 를 등록해보자 __ max-width 와 max-height 를 주고 overflow 를 줘야 나중에 확대/축소 했을 때 scroll 생기며 안 깨질 것이다.

그리고 pdfFile 과, scale 을 넘겨주자
      <section className="w-full max-h-[635px] max-w-[1470px] overflow-x-auto overflow-y-auto">
        <PdfViewer filePath={pdfFile} scale={scale} />
      </section>

 

ContentsViewerComponent.tsx
export default function ContentsViewerComponent({ className }: { className: string }) {
  ...
  const { data, isLoading } = usePdfGetQuery(path); //! query Get
  
  const [pdfFile, setPdfFile] = useState<string>("");

   useEffect(() => {
     if (!isLoading && data && data.fileContent) {
       const fileURL = base64Decoding(data.fileContent);
       setPdfFile(fileURL);
     }
   }, [data, isLoading]);

  //! PDF Zoom_____________________________________________________________________
  const [scale, setScale] = useState<number>(1);

  // 최소는 0.2 , 최대는 2.4  까지
  const handlePdfZoomOut = () => {
    setScale(prevScale => Math.max(prevScale - 0.2, 0.2));
  };

  const handlePdfZoomIn = () => {
    setScale(prevScale => Math.min(prevScale + 0.2, 2.4));
  };

  return (
    <div className={`${className} flex flex-col`}>
      <section className="flex justify-between items-center text-16 font-600 border-b-[1px] border-gray-300 pb-2 px-2">
		...

        <div className="flex items-center">
          <div className="w-16" />

          <div className="flex items-center">
            <TbFileSearch className="text-bgBlue text-22 mr-1" />
            <p className="text-20 text-bgBlue font-600 mr-3">Pdf Zoom</p>
            <FaMinusSquare
              onClick={handlePdfZoomOut}
              className="text-bgBlue text-30 hover:text-hoverColor transition cursor-pointer"
            />
            <div className="text-20 text-bgBlue font-600  ml-3 mr-2">
              {`${(scale * 100).toFixed(0)}%`}
            </div>
            <FaPlusSquare
              onClick={handlePdfZoomIn}
              className="text-bgBlue text-30 hover:text-hoverColor transition cursor-pointer"
            />
          </div>
        </div>
      </section>

      {/* pdf.js_viewer */}
      <section className="w-full max-h-[635px] max-w-[1470px] overflow-x-auto overflow-y-auto">
        <PdfViewer filePath={pdfFile} scale={scale} />
      </section>
    </div>
  );
}

 

 

 


 

이제 본격적 PdfViewer 로 넘어가보자!

 

 

CDN 등록 해주고

import { Document, Page, pdfjs } from "react-pdf";
import "react-pdf/dist/Page/TextLayer.css";

pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;

 

 

스크롤로 pdf 를 보여줄 것이기 때문에, 파일이 로드가 완료 되면 pdf 파일의 페이지 숫자 만큼

배열로 만들어서 랜더링 할 거다.

  function onDocumentLoadSuccess({ numPages }: { numPages: number }) {
    setNumPages(numPages);
  }

 

로딩이나 에러는 전체 스피너 처리 했다. 

UX 적으로 보기 안좋았다. 로딩에 에러도 뜨고,,

 

pdfViewer 코드_
      <div onContextMenu={e => e.preventDefault()}> // 우클릭 방지
        <Document
          file={filePath} // 받은 pdf filePath
          onLoadSuccess={onDocumentLoadSuccess}
          loading={PdfLoading}
          error={PdfLoading}>
          {/* scroll pdf */}
          {Array.from(new Array(numPages), (_, i) => (
            <Page
              width={1200}
              key={i}
              pageNumber={i + 1}
              loading={PdfLoading}
              error={PdfLoading}
              renderAnnotationLayer={false} // 양식 렌더링 false_ pdf 페이지 간격마다 빈공간 삭제
              scale={scale} // 받은 scale 적용
            />
          ))}
        </Document>
      </div>

 

각각의 세부 옵션은 문서의 

User guide

 

부분을 참조하자!

 

 

PdfViewer.tsx
"use client";

import React, { useState } from "react";
import Spinner from "@/components/common/spinner/Spinner";

//! import ㄱㄱ
import { Document, Page, pdfjs } from "react-pdf";
import "react-pdf/dist/Page/TextLayer.css";

pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;

function PdfViewer({
  filePath,
  scale,
}: {
  filePath: string;
  scale: number;
}) {
  const [numPages, setNumPages] = useState<number>(0);

  //! fn _____________________________________________________________________________
  function onDocumentLoadSuccess({ numPages }: { numPages: number }) {
    setNumPages(numPages);
  }

  const PdfLoading = () => {
    return <Spinner />;
  };

  return (
    // 우클릭 방지
    <>
      <div onContextMenu={e => e.preventDefault()}>
        <Document
          file={filePath}
          onLoadSuccess={onDocumentLoadSuccess}
          loading={PdfLoading}
          error={PdfLoading}>
          {/* scroll pdf */}
          {Array.from(new Array(numPages), (_, i) => (
            <Page
              width={1200}
              key={i}
              pageNumber={i + 1}
              loading={PdfLoading}
              error={PdfLoading}
              renderAnnotationLayer={false} // 양식 렌더링 false_ pdf 페이지 간격마다 빈공간 삭제
              scale={scale}
            />
          ))}
        </Document>
      </div>
    </>
  );
}

export default React.memo(DesignInfoPdfViewer);

 

 


 

결과화면