前端实现PDF预览的几种选择(pdfjs-dist、react-pdf、pdf-viewer)

记录

PDF预览的选型

对于浏览器自带的PDF预览

如果能直接使用,那自然最好不过了,但考虑多种因素,比如权限问题,禁止用户去下载PDF、预览样式不统一(不同浏览器PDF预览的实现不同),所有最终放弃了该方式

看了很多例子,大部分都是围绕pdf.js这个库展开的,所以我的选项也是围绕它去找的

最终找到几个不错的

  • pdfjs-dist
  • react-pdf
  • pdf-viewer

接下来我会依次介绍一下三个库的使用

pdfjs-dist

其实就是pdfjs库,只是对其进行打包发布到npm了

直接根据官方文档的案例对比进行操作就行了

Examples

typescript 复制代码
import { useEffect, useRef } from 'react'
import * as PDFJS from 'pdfjs-dist'

PDFJS.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.js`

interface Props {
  fileUrl: string
}

const FilePDF = ({ fileUrl }: Props) => {
  const pdfContainer = useRef<HTMLCanvasElement>(null)
  const pdfCtx = useRef<CanvasRenderingContext2D | null>(null)
  const pdfDoc = useRef<any>()
  const pdfNumPages = useRef(0)

	// 依次渲染所有页面
  const renderPage = num => {
    pdfDoc.current!.getPage(num).then(page => {
      const viewport = page.getViewport({ scale: 1 })
      pdfContainer.current!.width = viewport.width
      pdfContainer.current!.height = viewport.height

      page
        .render({
          viewport,
          canvasContext: pdfCtx.current!
        })
        .promise.then(() => {
          if (num < pdfNumPages.current) {
            renderPage(num + 1)
          }
        })
    })
  }

  useEffect(() => {
    pdfCtx.current = pdfContainer.current!.getContext('2d')

    PDFJS.getDocument(fileUrl).promise.then(pdfDoc_ => {
      pdfDoc.current = pdfDoc_
      pdfNumPages.current = pdfDoc_.numPages
      renderPage(1)
    })
  }, [])

  return (
    <div className={'flex h-full w-full items-center justify-center rounded-lg'}>
      <canvas ref={pdfContainer}></canvas>
    </div>
  )
}

export default FilePDF

这种实现比较繁琐,所以也就有了react-pdf,对pdfjs-dist进行了一层封装

效果展示

react-pdf

这种相对于原生pdfjs,简单了很多

typescript 复制代码
import { useRef, useState } from 'react'
import { Document, Page, pdfjs } from 'react-pdf'
import 'react-pdf/dist/Page/AnnotationLayer.css'
import 'react-pdf/dist/Page/TextLayer.css'

pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.js`

interface Props {
  fileUrl: string
}

const FilePDF = ({ fileUrl }: Props) => {
  const documentRef = useRef<HTMLDivElement>()
  const scale = useRef(1)
  const [pageNumber, setPageNumber] = useState<number>(1)

  const renderDocumentPage = (num: number, total: number) => {
    if (num <= total) {
      setPageNumber(num)
      requestIdleCallback(() => renderDocumentPage(num + 1, total))
    }
  }

  const onDocumentLoadSuccess = ({ numPages, ...rest }: { numPages: number }) => {
    requestIdleCallback(() => renderDocumentPage(1, numPages))
  }

  return (
    <div
      className={
        'flex h-full w-full justify-center overflow-auto rounded-lg bg-[#525659]'
      }
    >
      <Document
        ref={documentRef}
        file={fileUrl}
        onLoadSuccess={onDocumentLoadSuccess}
        loading="努力加载中..."
        renderMode="canvas"
      >
        {Array.from({ length: pageNumber }).map((_, i) => (
          <Page pageNumber={i + 1} className="mt-6" loading="努力加载中..." />
        ))}
      </Document>
    </div>
  )
}

export default FilePDF

但是,功能太少了,如果需要添加都要自己实现一遍,也很繁琐,所以还是用了pdfjs提供的viewer来实现这个效果的

这边的效果和pdfjs-dist呈现的是一样的

pdf-viewer

提示:使用的环境是 Vite + React

首先先根据自己的需求下载对应的build包

Getting Started

解压后,将其中的buildweb文件夹移入public中,也便后续能够直接在线上进行访问

这样就将 pdfjs 和 viewer 加载进来了,你可以启动项目到 /web/viewer.html 路径下访问,测试是否生效

接下来,我们对其进行封装,我通过的方式是iframe去访问 viewer 来展示pdf的

typescript 复制代码
interface Props {
  fileUrl: string
}

const FilePDF = ({ fileUrl }: Props) => {
  return (
    <div className={'h-full w-full overflow-hidden rounded-lg'}>
      <iframe
        className="border-0"
        title="预览文档"
        src={`/graphicPlatform/web/viewer.html?file=${encodeURIComponent(fileUrl)}`}
        width="100%"
        height="100%"
      ></iframe>
    </div>
  )
}

export default FilePDF

注意:

因为文件路径是一个url链接,不能直接当作链接,需要对其特殊字符进行转义,不然 viewer 没办法识别到真正的url

接着,我们还要到viewer里去修改一下接收到的file字符串,进行还原

这样 viewer 才能真正接收到fileUrl

最终呈现

encodeURIencodeURIComponent 的区别

encodeURIComponent() 函数 与 encodeURI() 函数的区别之处,前者假定它的参数是 URI 的一部分(比如协议、主机名、路径或查询字符串)。因此 encodeURIComponent() 函数将转义用于分隔 URI 各个部分的标点符号。

Viewer

再回到一开始的问题,我们需要禁用用户下载、打印等等功能,所以我们需要进入到 viewer 代码里进行删除对应的功能

首先在 viewer 中删除相关元素

viewer.html

viewer.mjs

删除无用文件

/web/locale

最终呈现

参考链接

  1. 前端 pdf 在线预览 - 掘金 (juejin.cn)
  2. *blog.csdn.net/qq_40609533...
  3. PDF.js (mozilla.github.io)
相关推荐
默默学前端25 分钟前
ES6模板语法与字符串处理详解
前端·ecmascript·es6
lxh011334 分钟前
记忆函数 II 题解
前端·javascript
我不吃饼干41 分钟前
TypeScript 类型体操练习笔记(三)
前端·typescript
华仔啊44 分钟前
除了防抖和节流,还有哪些 JS 性能优化手段?
前端·javascript·vue.js
CHU7290351 小时前
随时随地学新知——线上网课教学小程序前端功能详解
前端·小程序
清粥油条可乐炸鸡1 小时前
motion入门教程
前端·css·react.js
这是个栗子1 小时前
【Vue3项目】电商前台项目(四)
前端·vue.js·pinia·表单校验·面包屑导航
前端Hardy1 小时前
Electrobun 正式登场:仅 12MB,JS 桌面开发迎来轻量化新方案!
前端·javascript·electron
树上有只程序猿1 小时前
新世界的入场券,不再只发给程序员
前端·人工智能
confiself1 小时前
deer-flow前端分析
前端