前端使用pdf.js渲染pdf文件解决方案

一、前言

在前端开发中,渲染PDF文件一直是一项重要而挑战性的任务。而今,我们可以借助pdf.js库来轻松实现这一目标。pdf.js是一个开源的JavaScript库,它可以在浏览器中渲染PDF文件,实现了在网页上查看PDF文档的功能。它提供了丰富的API和功能,使得在前端页面展示PDF文件变得轻而易举。让我们一起探索pdf.js的奇妙之处,轻松实现前端PDF文件的渲染与展示吧!

二、简介

1、pdf.js介绍

pdf.js是一款基于JavaScript的开源PDF阅读器组件,可以在网页中直接显示和操作PDF文件,目前已知的前端渲染pdf组件都是基于pdf.js进行封装。

git地址:https://github.com/mozilla/pdf.js

注:开源且免费

2、插件版本参数

插件版本
Nodev22.13.0

@types/react

^18.0.33

@types/react-dom

^18.0.11
pdfjs-2.5.207-es5-dist.zip (viewer.js使用方式)2.5.207
pdfjs-dist   (canvas渲染方式)

3.6.172

三、通过viewer.html实现预览(推荐)

1、介绍

除了PDF预览,还待配套的工具栏,支持功搜索、缩放、目录、打印等功能~ 

Demo如图:

2、部署

【1】下载插件包

下载地址:https://github.com/mozilla/pdf.js/releases/tag/v2.5.207

【2】客户端方式

把下载后的pdfjs-2.5.207-es5-dist.zip解压后,放在项目中的public文件夹下

【3】服务端方式

 pdf.js包仍然放在public目录下(或者服务端目录下)

const pdfServerUrl = '/pdfjs-2.5.207-es5-dist/web/viewer.html'

一个可以获取pdf文件二进制流的地址(需要同源

....
const pdfInfoUrl = `${location.origin}/xxx/xx.pdf`;
const url = `${pdfServerUrl}?file=${encodeURIComponent(pdfInfoUrl)}`
...
<iframe id='pdfIframe' src={url} width="100%" height="100%"></iframe>

3、使用方法

【1】预览PDF文件

1)客户端方式(基于React框架为例)

const viewPDF: React.FC = () => {
    // pdf文件路径,放在项目的public目录下
    const pdfUrl = '/A.pdf'; 
    //pdf.js库的代码,放在项目的public目录下
    const pdfServerUrl = '/pdfjs-2.5.207-es5-dist/web/viewer.html'
    const url = `${pdfServerUrl}?file=${pdfUrl}`
  
    return <>
        <h1>pdf 搜索(基于pdf-dist,pdf_viewer.html)</h1>
        <iframe id='pdfIframe' src={url} width="100%" height="100%"></iframe>
      </>;
}

2)服务端方式

通过axios接口获取文件的arraybuffer,再把arraybuffer转换成二进制Blob,最后把Bolb转成blob:url传给viewer.html

...
import axios from 'axios';
...
const [pdfUrl, setPdfUrl] = useState<string>('');
const getPDFViewUrl = (fileName: any) => {
  axios({
    method: 'get',
    url: `URL`,//文件下载的url
    responseType: 'arraybuffer' 
  }).then(response =>{
      const blob = new Blob([response.data], { type: 'application/pdf' });
      const blobUrl = URL.createObjectURL(blob);
      console.log('zyk===>', blobUrl)
      setPdfUrl(`${pdfServerUrl}?file=${blobUrl}`)
    })
 };

return (
  <>
    <iframe id="pdfIframe" src={pdfUrl} width="100%" height="100%"></iframe>
  </>
);

【2】外部搜索条件触发pdf.js的搜索逻辑

  • 跳转至第一个匹配的内容
  • 匹配内容高亮
const viewPDF: React.FC = () => {
    // pdf文件路径,放在项目的public目录下
    const pdfUrl = '/A.pdf'; 
    //pdf.js库的代码,放在项目的public目录下
    const pdfServerUrl = '/pdfjs-2.5.207-es5-dist/web/viewer.html'
    const url = `${pdfServerUrl}?file=${pdfUrl}`

    let pdfContentWindow: any = null; //缓存iframContent
    const getPdfContent = () => {
        const pdfFrame: any = document.getElementById('pdfIframe');
        if (!pdfFrame) {
          return;
        }
        pdfContentWindow = pdfFrame.contentWindow;
        //pdf组件部分信息,包括:当前页码、总共页码等
        console.log('page===>', pdfContentWindow.PDFViewerApplication); 
    }
    const onSearchForOut = (searchText: string) => {
        pdfContentWindow.postMessage(searchText, '*');
        pdfContentWindow.addEventListener('message', (e: any) => {
          
          // 高亮匹配结果
          pdfContentWindow.PDFViewerApplication.findBar.findField.value = e.data;
          pdfContentWindow.PDFViewerApplication.findBar.highlightAll.checked = true;                        pdfContentWindow.PDFViewerApplication.findBar.dispatchEvent('highlightallchange');
          //触发搜索项‘下一个'事件
          pdfContentWindow.PDFViewerApplication.findBar.dispatchEvent('again', false);

    }, false);
  }
  useEffect(() => {
    getPdfContent();
    setTimeout(() => {
      // 外部的搜索条件
      onSearchForOut('阳区CBD核心区')
    }, 3* 1000)
  }, []);  


    return <>
        <h1>pdf 搜索(基于pdf-dist,pdf_viewer.html)</h1>
        <iframe id='pdfIframe' src={url} width="100%" height="100%"></iframe>
      </>;
}

四、把pdf渲染为canvas实现预览

1、安装

npm install pdfjs-dist --save

2、功能实现

【1】实现pdf预览

import { Button } from 'antd';
import { useState, useEffect, useRef } from 'react';
import * as pdfjsLib from 'pdfjs-dist'; // 引入pdfjs-dist
const pdfUrl = '/zyk.pdf'; // pdf 文件路径,pdf文件存放于public目录下
const workerUrl = `/pdf.worker.min.js`; //webworker存放于public目录下
pdfjsLib.GlobalWorkerOptions.workerSrc = workerUrl;

const viewPdf = (props: {height: string}) => {
  const {height} = props;
  const pdfContainerRef = useRef<any>(null);
  const [pagesList, setPagesList] = useState<any>([]);
  const scale = 2; // 缩放比例
  // 渲染单个页面
  const renderPage = async (page: any, pageNumber: number) => {
    const viewport = page.getViewport({ scale });
    const pageContentDom = document.createElement('div');
    pageContentDom.id = `pdfPage-content-${pageNumber}`;
    pageContentDom.style.width = `${viewport.width}px`;
    pageContentDom.style.height = `${viewport.height}px`;
    pageContentDom.style.position = 'relative';
    // 创建 Canvas 元素
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    canvas.id=`pdfPage-${pageNumber}`
    canvas.width = viewport.width;
    canvas.height = viewport.height;
    canvas.style.border = '1px solid black';
    pageContentDom.appendChild(canvas);
    pdfContainerRef.current.appendChild(pageContentDom);
    // 渲染 PDF 页面到 Canvas
    await page.render({
      canvasContext: context,
      viewport,
    }).promise;
  };
  // 渲染 PDF 页面
  const renderPagesGroup = ( pages: any) => {
    pages.forEach(({page}:any, index: number) => {
      renderPage(page, index);
    });
  };
  // 加载 PDF 文件
  const loadPdf = async (url: any) => {
    const pdf = await pdfjsLib.getDocument(url).promise;
    const pages: any[] = [];
    for (let i = 1; i <= pdf.numPages; i++) {
      const page = await pdf.getPage(i);
      const textContent = await page.getTextContent();
      pages.push({
        page,
        textContent
      });
    }
    setPagesList(pages);
    renderPagesGroup(pages);
  };
  useEffect(() => {
    loadPdf(pdfUrl);
  }, []);
  return <>
      <div>
          <h1>PDF 搜索(基于@pdfjs-dist-自定义实现)</h1> 
      <div>
      <div style={{ height: height || '500px' }}>
        {/* PDF 容器 *