React中使用pdfjs预览pdf格式文件

1,171 阅读2分钟

React中使用pdfjs预览pdf格式文件

实习中遇到一个需求,需要在前端预览pdf,word,和excel文件,因为pdf预览的资料比较多,先做了pdf预览的,使用的pdfjs-dist,踩坑及过程如下。

应用环境

vite@4.4.5
react@18.2.0
pdfjs-dist@2.12.313

踩坑1

用yarn add pdfjs-dist不下来包,必须指定版本

踩坑2

在引入psfjs-dist的时候,遇到很多奇奇怪怪的问题,先给出我下面可行的代码

import React from 'react';
import * as PdfJs from 'pdfjs-dist/legacy/build/pdf.js';
import { useState, useEffect, useCallback } from 'react';
import Axios from 'axios';
const pdfjsWorker = await import('pdfjs-dist/build/pdf.worker.entry');
// 设定pdfjs的 workerSrc 参数
// NOTE: 这一步要特别注意,网上很多关于pdfjs的使用教程里漏了这一步,会出现workerSrc未定义的错误
PdfJs.GlobalWorkerOptions.workerSrc = pdfjsWorker;

如果没有设置workerSrc参数,后面使用会报错404,很多人使用的webpack,因此他们用的require('pdfjs-dist/build/pdf.worker.entry'),但是我用的vite,是不支持require的,我尝试安装了vite-plugin-require-transform,但是它在react里似乎不起作用。后来查了vite的引入静态资源的方式,最终使用了import('pdfjs-dist/build/pdf.worker.entry')成功

下面就是具体实现代码啦。

  1. 这里是封装的一个选择文件的组件,因为我后面还要写docx,excel格式的文件。做的步骤就是,获取到input框选择的file文件,解析这个文件让其变成arrayBuffer格式,再用pdfjs去预览
import './index.css';
import { readBuffer, getExtend } from '@/components/fileView/utils.js';
import Pdf from '@/vendors/pdf/Pdf';
const Index = () => {
  const [type, setType] = useState(null);
  // const [file, setFile] = useState({});
  const [loading, setLoading] = useState(false); //加载动画
  const [arrayBuffer, setArrayBuffer] = useState({});
  let buffer;
  const handleChoosed = async (e) => {
    setLoading(true);
    try {
      //解构赋值,e.target.files获取第一个元素
      const [file] = e.target.files;
      // console.log(test);
      // console.log(e.target.files);
      buffer = await readBuffer(file);
      const { name } = file;
      setType(getExtend(name));
      setArrayBuffer(buffer);
      console.log(arrayBuffer);
      setLoading(false);
    } catch (e) {
      console.error(e);
    } finally {
      setLoading(false);
    }
  };
  return (
    <div className="pdf-wrap">
      <input type="file" onChange={(e) => handleChoosed(e)}></input>
      <div className="pdf-container">
        {/* <canvas id="pdf-canvas"></canvas> */}
        {type && <Pdf data={arrayBuffer} />}
        {/* <Pdf data={arrayBuffer} /> */}
      </div>
    </div>
  );
};

export const readBuffer = async (file) => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = (loadEvent) => resolve(loadEvent.target.result);
    reader.onerror = (e) => reject(e);
    reader.readAsArrayBuffer(file);
  });
};

export function getExtend(name) {
  const dot = name.lastIndexOf('.');
  return name.substr(dot + 1);
}
  1. pdfjs渲染获取到的pdf文件。暂时只能单页渲染,点击上一页下一页时渲染新一页的内容
import React from 'react';
import * as PdfJs from 'pdfjs-dist/legacy/build/pdf.js';
import { useState, useEffect, useCallback } from 'react';
import Axios from 'axios';
const pdfjsWorker = await import('pdfjs-dist/build/pdf.worker.entry');
PdfJs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
let pdfDoc;

const Pdf = ({ data }) => {
  const [pageNum, setPageNum] = useState(1);
  const [total, setTotal] = useState(1);
  const [scale, setScale] = useState(1.5);
  const loadFile = async () => {
    //初始化pdf
    console.log('初始化pdf');
    pdfDoc = await PdfJs.getDocument(data).promise;
    // console.log(pdfDoc.numPages);
    setTotal(pdfDoc.numPages);
    // console.log(pdfDoc);
  };
  
  const renderPdf = async (currentPageNum, currentScale) => {
    console.log('渲染pdf页');
    pdfDoc.getPage(currentPageNum).then((pageContent) => {
      let canvas = document.getElementById(`canvas+${currentPageNum}`);
      // let vp = pageContent.getViewport({ scale: currentScale });
      // console.log(canvas);
      let ctx = canvas.getContext('2d');
      let dpr = window.devicePixelRatio || 1;
      let bsr =
        ctx.webkitBackingStorePixelRatio ||
        ctx.mozBackingStorePixelRatio ||
        ctx.msBackingStorePixelRatio ||
        ctx.oBackingStorePixelRatio ||
        ctx.backingStorePixelRatio ||
        1;
      let ratio = dpr / bsr;
      let viewport = pageContent.getViewport({
        scale: currentScale,
      });
      canvas.width = viewport.width * ratio;
      canvas.height = viewport.height * ratio;
      canvas.style.width = viewport.width + 'px';
      canvas.style.height = viewport.height + 'px';
      ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
      let renderContext = {
        canvasContext: ctx,
        viewport: viewport,
      };
      pageContent.render(renderContext);

      // setPageNum(currentPageNum + 1);
      // setScale(currentScale);
    });
  };
  const handleScaleAdd = () => {
    //增大缩放倍数
    setScale(scale + 0.1);
  };
  function handleScaleSub() {
    //减小缩放倍数
    if (scale > 0.5) {
      //小于0.5不再进行缩小
      setScale(scale - 0.1);
    }
  }

  function prePage() {
    if (pageNum > 1) {
      setPageNum(pageNum - 1);
    }
  }

  function nextPage() {
    if (total > pageNum) {
      setPageNum(pageNum + 1);
    }
  }
  useEffect(() => {
    loadFile();
  }, [pageNum, scale, data]);

  return (
    <div>
      <div className="pdf-container">
        <div className="pdf-control-zoom">
          <button onClick={prePage}>上一页</button>
          <button onClick={nextPage}>下一页</button>
        </div>
        <canvas id={`canvas+${pageNum}`}></canvas>
      </div>
      <div className="pdf-control-zoom">
        <button onClick={() => handleScaleAdd()} value="">
          放大
        </button>
        <button onClick={() => handleScaleSub()} value="">
          缩小
        </button>
      </div>
    </div>
  );
};

export default Pdf;

接下来会去实现按照滚动自动加载下一页,以及优化代码,菜鸟本鸟还是太菜了

参考链接:React实现预览PDF - 掘金 (juejin.cn)

(50条消息) 纯前端文档预览,还要支持所有主流格式,有这一篇就足够了_前端文件预览_小爬的老粉丝的博客-CSDN博客