使用 React 和 pdf.js 创建高效的 PDF 查看器

223 阅读2分钟

我们将介绍如何使用 pdf.js 在 React 应用中构建一个简约的 PDF 查看器。就是一张一张图片拼起来的。

上代码

import { useEffect, useRef, useState } from 'react';import * as PDFJS from 'pdfjs-dist';import 'react-pdf/dist/esm/Page/AnnotationLayer.css';PDFJS.GlobalWorkerOptions.workerSrc =  'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.6.82/pdf.worker.min.mjs';interface Props {  fileUrl: string;}const PDFViewer = ({ fileUrl }: Props) => {  const [numPages, setNumPages] = useState<number>(0); // 页数状态  const pdfRefs = useRef<(HTMLCanvasElement | null)[]>([]); // 用于存储每一页的canvas引用  const observer = useRef<IntersectionObserver | null>(null); // IntersectionObserver引用  // 渲染指定页面  const renderPage = (page: any, canvas: HTMLCanvasElement) => {    const viewport = page.getViewport({ scale: 1 });    if (canvas) {      // 确保canvas有效      canvas.width = viewport.width;      canvas.height = viewport.height;      const pdfCtx = canvas.getContext('2d');      if (pdfCtx) {        page.render({          canvasContext: pdfCtx,          viewport: viewport,        });      }    }  };  // 处理页面加载  const loadPage = (index: number) => {    if (index < numPages) {      PDFJS.getDocument(fileUrl).promise.then((pdfDoc) => {        pdfDoc.getPage(index + 1).then((page) => {          const canvas = pdfRefs.current[index];          if (canvas) {            // 确保canvas不为null            renderPage(page, canvas); // 渲染页面          }        });      });    }  };  useEffect(() => {    // 获取PDF文档    PDFJS.getDocument(fileUrl).promise.then((pdfDoc) => {      setNumPages(pdfDoc.numPages); // 设置页面数量      // 创建Intersection Observer      observer.current = new IntersectionObserver((entries) => {        entries.forEach((entry) => {          if (entry.isIntersecting) {            const index = Number(entry.target.getAttribute('data-index'));            loadPage(index); // 加载页面            observer.current?.unobserve(entry.target); // 加载后取消观察          }        });      });      // 观察每一页      pdfRefs.current.forEach((canvas, index) => {        if (canvas) {          canvas.setAttribute('data-index', index.toString());          observer.current?.observe(canvas); // 使用可选链确保不是null        }      });    });    return () => {      observer.current?.disconnect(); // 使用可选链确保不是null    };  }, [fileUrl, numPages]);  return (    <div      style={{        display: 'flex',        flexDirection: 'column',        width: '100%',        height: '100%',        overflowY: 'auto',      }}    >      {Array.from({ length: numPages }, (_, index) => (        <canvas          key={index}          ref={(el) => (pdfRefs.current[index] = el)} // 存储每一页的canvas引用          style={{ marginBottom: '20px', display: 'block' }} // 添加每页之间的间隔        ></canvas>      ))}    </div>  );};export default PDFViewer;

主要还是注意这段代码,在使用之前先看看这个链接能不能访问。无法访问的话就重新去pdf.js

官网重新拿一个,标记了框框的那个是pdfjs-dist的版本号。

为了避免一次性渲染所有 PDF 页面,我引入了 Intersection Observer。当用户滚动浏览 PDF 文档时,只有当前视口中的页面才会被渲染,这大大减少了不必要的资源消耗。

我也是第一次写预览pdf这个需求,有啥不好的地方大家给我点建议