

React 实现 PDF 文件在线预览 - 手把手教你写 React PDF 预览功能
source link: https://kalacloud.com/blog/how-build-react-pdf-viewer-pdfjs/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在 React 项目中,很多场景都需要 PDF 文件预览功能,比如合同 ERP,销售CRM,内部文档 CMS 管理系统,都需要内置 PDF 文件在线预览功能。本文手把手教你搭建一套 PDF 预览组件嵌入到 React 项目中,实现 PDF 文件预览的所有常见功能。
跟随本教程学习完成后,你会搭出以下 PDF 在线预览效果的 React PDF 预览组件

如果你正在搭建后台管理工具,又不想处理前端问题,推荐使用卡拉云,卡拉云是新一代低代码开发工具,可一键接入常见数据库及 API ,无需懂前端,仅需拖拽即可快速搭建属于你自己的后台管理工具,一周工作量缩减至一天,详见本文文末。
本次教程中使用的技术栈
- React
- Typescript
- pdf.js
快速搭建项目
> yarn create vite pdf-preview --template react-ts
现在我们安装下 pdf.js
通过官网的介绍,并没有发现 npm 的下载方式,这时候很多人估计就会直接安装 umd 版本的了,其实使用一个库除了看文档,看官方案例也是非常重要的,通过源代码下的 examples/webpack/main.js
文件,我们看到 pdfjs-dist
这个npm包,我们来下载
> yarn add pdfjs-dist
然后按照自己的习惯组织下文件目录
.
├── 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} />
)
}
代码简单讲解下
- getDocument 去请求pdf的内容
- getPage 获取对应页面的内容
- 使用 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,
}
}
接下来我们实现滚动翻页功能
- 点击对应页滚动到指定的位置
- 滚动到对应位置,高亮当前页
先看下最终的效果

首先实现点击滚动到对应的位置,非常的简单,利用 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 预览功能。如果不想处理前端问题,推荐使用卡拉云,卡拉云内置各类组件,无需懂任何前端,仅需拖拽即可快速生成。
下图为使用卡拉云搭建的内部广告投放监测系统,仅需拖拽,1小时搞定。
再看个卡拉云的 Demo 案例,下面是用卡拉云搭建的数据库 CURD 后台管理系统,只需拖拽组件,即可在10分钟内完成搭建。
可直接分享给同事一起使用:https://my.kalacloud.com/apps/8z9z3yf9fy/published
卡拉云是新一代低代码开发平台,与 React 这类框架相比,卡拉云无需配置开发环境,直接注册即可开始搭建。开发者无需处理任何前端问题,简单拖拽即可生成图表、表格、表单、富文本等功能组件,一键接入数据库及 API,快速完成企业内部工具搭建,还可以分享给团队成员共享使用,数周的开发时间,缩短至 1 小时。
Recommend
-
51
vue版本裁切工具,包含预览功能 最终效果: qiuyaofan.github.io/vue-crop-de… 源码地址: github.com/qiuyaofan/v… 第一步:先用vue-cli安装脚手架(不会安装的看 vue-cli官网) // 初始化
-
8
手把手教你实现macOS应用文件拖拽进窗口功能-批量生成APP的多尺寸icon实战 | Just My Blog手把手教你实现macOS应用文件拖拽进窗口功能-批量生成APP的多尺寸icon实战 ...
-
16
概念 GitHub Actions 是 GitHub 于2018年10月推出的持续集成服务。 那么何谓持续集成...
-
8
手把手教你在线制作亚马逊要的发票(形式发票)-跨境头条-AMZ123亚马逊导航-跨境电商出海门户 手把手教你在线制作亚马逊要的发票(形式发票)...
-
6
Spring Boot的上传文件,相信你一定会了。如果还不会的小伙伴,可以先看看之前的分享: 文件上传实现之后,通常最常见的另外两个操作就是下载和预览,下载只需要知道地址,就简单搞定了,那么预览怎么做?你知道吗? 今天小编就来推荐一个用Spri...
-
5
本文由ELab团队技术团队分享,原题“Twitter和微博都在用的 @ 人的功能是如何设计与实现的?”,有修订。第一次使用@人功能到现在已经有差不多10年了,初次使用是通过微博体验的。@人的功能现在遍布各种应用,基本上涉及社交(IM、微博)、办公(钉钉、企...
-
8
文件文档在线预览-kkFileView – 开源派 kkFileView是国人开源的一款文件文档在线预览项目,支持主流办公文档的在线预览,如 doc、docx、Excel、pdf、txt、zip、rar、 图片等格式。项目基于spring boot搭建,遵守Apache 2.0开源协议...
-
6
本文是js文件导出系列第二期,主要讲讲如何在线导出pdf文件,及office文件如何在线预览。目前前端实现这些技术已经比较成熟,都有比较成熟的技术库,下面来和大家一起过一下吧。 纯前端导出pdf 这里就不造轮子了,说一说纯前端导出pdf的几个流...
-
8
文档在线预览(二)word、pdf文件转html以实现文档在线预览 ...
-
11
手把手教你写一个JSON在线解析的前端网站1 ...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK