写点什么

React 实现 PDF 文件在线预览 - 手把手教你写 React PDF 预览功能

作者:蒋川
  • 2022 年 5 月 30 日
  • 本文字数:4587 字

    阅读完需:约 15 分钟

React 实现 PDF 文件在线预览 - 手把手教你写 React PDF 预览功能

本文完整版:《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 小时。立即免费试用卡拉云


扩展阅读:


发布于: 刚刚阅读数: 3
用户头像

蒋川

关注

我的微信:HiJiangChuan 2020.09.08 加入

卡拉云 CMO 卡拉云是一套帮助后端程序员搭建企业内部工具的系统,欢迎试用 www.kalacloud.com

评论

发布
暂无评论
React 实现 PDF 文件在线预览 - 手把手教你写 React PDF 预览功能_JavaScript_蒋川_InfoQ写作社区