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

介绍如何使用 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这个需求,写的不好还请见谅。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值