本文完整版:《React 实现 PDF 文件在线预览 - 手把手教你写 React PDF 预览性能》

在 React 我的项目中,很多场景都须要 PDF 文件预览性能,比方合同 ERP,销售CRM,外部文档 CMS 管理系统,都须要内置 PDF 文件在线预览性能。本文手把手教你搭建一套 PDF 预览组件嵌入到 React 我的项目中,实现 PDF 文件预览的所有常见性能。

追随本教程学习实现后,你会搭出以下 PDF 在线预览成果的 React PDF 预览组件

如果你正在搭建后盾管理工具,又不想解决前端问题,举荐应用卡拉云,卡拉云是新一代低代码开发工具,可一键接入常见数据库及 API ,无需懂前端,仅需拖拽即可疾速搭建属于你本人的后盾管理工具,一周工作量缩减至一天,详见本文文末。

本次教程中应用的技术栈

  • Vite
  • React
  • Typescript
  • pdf.js

疾速搭建我的项目

> yarn create vite pdf-preview --template react-ts

当初咱们装置下 pdf.js

通过官网的介绍,并没有发现 npm 的下载方式,这时候很多人预计就会间接装置 umd 版本的了,其实应用一个库除了看文档,看官网案例也是十分重要的,通过源代码下的 examples/webpack/main.js 文件,咱们看到 pdfjs-dist 这个npm包,咱们来下载

而后依照本人的习惯组织下文件目录

.├── components│   └── PDFRender│       └── index.tsx├── main.tsx├── App.tsx└── vite-env.d.ts

举荐浏览《5种 开源 react 挪动端 ui 组件库测评举荐》

渲染第一页 - React 开发预览组件

这里我新建了一个 PDFRender 组件,先来实现一个最简略的,将 PDF 的第一页渲染进去

import * as pdf from 'pdfjs-dist'import pdfWorker from 'pdfjs-dist/build/pdf.worker.js?url'import React, { useLayoutEffect, useRef } from "react";pdf.GlobalWorkerOptions.workerSrc = pdfWorker;export const PDFRender: React.FC<{ src: string }> = (props) => {  const canvasRef = useRef<HTMLCanvasElement | null>(null)  useLayoutEffect(() => {    pdf    .getDocument(props.src)    .promise    .then(pdfDocument => {      return pdfDocument.getPage(1);    })    .then((pdfPage) => {      const viewport = pdfPage.getViewport({ scale: 1.0 });      const canvas = canvasRef.current;      if (!canvas) {        return Promise.reject()      }      canvas.width = viewport.width      canvas.height = viewport.height;      const ctx = canvas.getContext("2d") as CanvasRenderingContext2D      const renderTask = pdfPage.render({        canvasContext: ctx,        viewport,      });      return renderTask.promise;    })    .catch(err => {      console.log(err)    })  }, [])  return (    <canvas ref={canvasRef}/>  )}

仔细的同学可能发现了这两行代码

import pdfWorker from 'pdfjs-dist/build/pdf.worker.js?url'pdf.GlobalWorkerOptions.workerSrc = pdfWorker;

这是因为pdf的交互容易梗塞JS,所以 pdf.js 应用了 web worker 技术优化了性能。

最初咱们应用下这个组件,看下成果

import { PDFRender } from "./components/PDFRender";const pdfFilePath = '/kalacloud-demo.pdf'export const App = () => {  return (    <PDFRender src={pdfFilePath} />  )}

成果如下

代码简略解说下

  1. getDocument 去申请pdf的内容
  2. getPage 获取对应页面的内容
  3. 应用 canvas 绘制以后页面

扩大浏览:《顶级开源 react ui 组件库测评举荐》

渲染整个 PDF 并翻页 - React 开发预览组件

想渲染全副页面其实很简略,依照下面的思路,获取到页数,间接循环渲染就好了

import * as pdf from 'pdfjs-dist'import pdfWorker from 'pdfjs-dist/build/pdf.worker.js?url'import { useEffect, useRef, useState } from "react";pdf.GlobalWorkerOptions.workerSrc = pdfWorker;export const usePDFData = (options: { src: string, scale?: number }) => {  const previewUrls = useRef<string[]>([])  const urls = useRef<string[]>([])  const [loading, setLoading] = useState(true)  useEffect(() => {    urls.current = []    setLoading(true)    ;(async () => {      // 这里千万别解构,会导致 this 指向谬误      const pdfDocument = await pdf.getDocument(options.src).promise      const task = new Array(pdfDocument.numPages).fill(null)      await Promise.all(task.map(async (_, i) => {        const page = await pdfDocument.getPage(i + 1)        const viewport = page.getViewport({ scale: options.scale || 2 })        const canvas = document.createElement('canvas')        canvas.width = viewport.width        canvas.height = viewport.height        const ctx = canvas.getContext("2d") as CanvasRenderingContext2D        const renderTask = page.render({          canvasContext: ctx,          viewport,        });        await renderTask.promise;        // 别离获取不同尺寸的图片,一个用来预览一个用来展现        urls.current[i] = canvas.toDataURL('image/jpeg', 1)        previewUrls.current[i] = canvas.toDataURL('image/jpeg', 0.5)      }))      setLoading(false)    })()  }, [options.src])  return {    loading,    urls: urls.current,    previewUrls: previewUrls.current,  }}

接下来咱们实现滚动翻页性能

  1. 点击对应页滚动到指定的地位
  2. 滚动到对应地位,高亮当前页

先看下最终的成果

首先实现点击滚动到对应的地位,十分的简略,利用 scrollIntoView api 能够疾速定位到指定地位

  const goPage = (i: number) => {    setCurrentPage(i)    document.querySelectorAll('.page')[i]!.scrollIntoView({ behavior: 'smooth' })  }

再来实现下滚动地位主动高亮页数

实质上是应用 IntersectionObserver api 来实现,监听每个页面的可见性,当可见性大于 0.5 也就是有一半的内容展现在视口外面则就确定为当前页

  const io = useRef(new IntersectionObserver((entries) => {    entries.forEach(item => {      item.intersectionRatio >= 0.5 && setCurrentPage(Number(item.target.getAttribute('index')))    })  }, {    threshold: [0.5]  }))

扩大浏览:《顶级开源 react admin 后盾治理框架测评举荐》

PDF 文本抉择

在一些非凡场景,可能会须要反对用户复制PDF上的文字,很显然 图片中的文字不能被选中。然而弱小的 pdf.js 反对在雷同的地位绘制文字,接下来咱们实现它

import * as pdf from 'pdfjs-dist'import pdfWorker from 'pdfjs-dist/build/pdf.worker.js?url'+ import { TextLayerBuilder } from 'pdfjs-dist/web/pdf_viewer';+ import 'pdfjs-dist/web/pdf_viewer.css';import { useEffect, useRef, useState } from "react";pdf.GlobalWorkerOptions.workerSrc = pdfWorker;export const usePDFData = (options: { src: string, scale?: number }) => {  const previewUrls = useRef<string[]>([])  const pages = useRef<{ canvas: HTMLCanvasElement, text: HTMLDivElement }[]>([])  const [loading, setLoading] = useState(true)  useEffect(() => {    pages.current = []    setLoading(true)    ;(async () => {      const pdfDocument = await pdf.getDocument(options.src).promise      const task = new Array(pdfDocument.numPages).fill(null)      await Promise.all(task.map(async (_, i) => {        const page = await pdfDocument.getPage(i + 1)        const viewport = page.getViewport({ scale: options.scale || 2 })        const canvas = document.createElement('canvas')        canvas.width = viewport.width        canvas.height = viewport.height        const ctx = canvas.getContext("2d") as CanvasRenderingContext2D        const renderTask = page.render({          canvasContext: ctx,          viewport,        });        await renderTask.promise;        previewUrls.current[i] = canvas.toDataURL('image/jpeg', 0.5) +       const textContent = await page.getTextContent() +       const textLayerDiv = document.createElement('div'); +       textLayerDiv.setAttribute('class', 'textLayer'); +       const textLayer = new TextLayerBuilder({ +         textLayerDiv, +         pageIndex: i + 1, +         viewport, +         eventBus: undefined +      }); + +       textLayer.setTextContent(textContent); +       textLayer.render();        pages.current[i] = {          canvas,          text: textLayerDiv        }      }))      setLoading(false)    })()  }, [options.src])  return {    loading,    pages: pages.current,    previewUrls: previewUrls.current,  }}

扩大浏览《React Echarts 应用教程 - 如何在 React 退出图表 》

React PDF 在线预览源代码

本次教程的代码能够在 github 上查看

如果你只须要预览 PDF 并且不关怀浏览器兼容,那么应用 embed 只须要一行代码就能实现
<!DOCTYPE html><html lang="en">  <head>    <meta charset="UTF-8" />    <link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <title>Vite App</title>    <style>      * {        padding: 0;        margin: 0;      }      body {        height: 100%;        width: 100%;        overflow: hidden;        background-color: rgb(82, 86, 89);      }      embed {        width: 100vw;        height: 100vh;      }    </style>  </head>  <body>  <embed src="/kalacloud-demo.pdf" type="application/pdf">  </body></html>

扩大浏览:《顶级 开源 react table 表格组件测评举荐》

React PDFjs 搭建总结及卡拉云

本文介绍了如何在 React 中实现 PDF 预览性能。如果不想解决前端问题,举荐应用卡拉云,卡拉云内置各类组件,无需懂任何前端,仅需拖拽即可疾速生成。

卡拉云可帮你疾速搭建企业外部工具,下图为应用卡拉云搭建的外部广告投放监测零碎,无需懂前端,仅需拖拽组件,10 分钟搞定。你也能够疾速搭建一套属于你的后盾管理工具,理解更多。

卡拉云是新一代低代码开发平台,与前端框架 Vue、React等相比,卡拉云的劣势在于不必首先搭建开发环境,间接注册即可开始应用。开发者齐全不必解决任何前端问题,只需简略拖拽,即可疾速生成所需组件,可一键接入常见数据库及 API,依据疏导简略几步买通前后端,数周的开发工夫,缩短至 1 小时。立刻收费试用卡拉云。

扩大浏览:

  • React Router 6 (React路由) 最具体教程
  • 全栈实战:React + Nodejs 搭建带预览的「上传图片/预览」治理后盾
  • React Draggable 实现拖拽 - 最具体中文教程
  • 最好用的 8 款 React Datepicker 工夫日期选择器测评举荐
  • React form 表单验证终极教程