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

news/2024/7/15 18:06:15 标签: react.js, 前端, javascript

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

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

React 实现 PDF 文件在线预览

    • 快速搭建项目
    • 渲染第一页 - React 开发预览组件
    • 渲染整个 PDF 并翻页 - React 开发预览组件
    • PDF 文本选择
    • React PDF 在线预览源代码
    • React PDFjs 搭建总结及卡拉云

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

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

React PDFjs 搭建效果

如果你正在搭建后台管理工具,又不想处理前端问题,推荐使用卡拉云,卡拉云是新一代低代码开发工具,可一键接入常见数据库及 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} />
  )
}

效果如下

react嵌入pdfjs

代码简单讲解下

  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. 滚动到对应位置,高亮当前页

先看下最终的效果 React PDFjs 搭建效果

首先实现点击滚动到对应的位置,非常的简单,利用 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 表单验证终极教程

http://www.niftyadmin.cn/n/1281339.html

相关文章

网页制作实验内容

实验一 网站建设与管理 实验二 图文混排 实验三 网页布局 实验四 导航制作与使用 转载于:https://blog.51cto.com/dyzyxy/1004669

Linux IPC开发者性能测试

http://blog.chinaunix.net/uid-190176-id-4177874.html 一. 概述 Linux/UNIX发展数十年&#xff0c;IPC可谓五花八门&#xff0c;好在后来POSIX和SUS标准化下了很多功夫&#xff0c;如今接口清晰稳定了不少&#xff0c;但各系统实现依然有不少大坑小坑&#xff0c;不仅要看书…

速读原著-TCP/IP(IP选路)

第9章 IP选路 9.1 引言 选路是I P最重要的功能之一。图 9 - 1是I P层处理过程的简单流程。需要进行选路的数据报可以由本地主机产生&#xff0c;也可以由其他主机产生。在后一种情况下&#xff0c;主机必须配置成一个路由器&#xff0c;否则通过网络接口接收到的数据报&#…

Vue3 Typescript + Axios 全栈开发教程:手把手教你写「待办清单」APP

本文完整版&#xff1a;《Vue3 Typescript Axios 全栈开发教程&#xff1a;手把手教你写「待办清单」APP》 Vue3 Typescript Axios 全栈开发教程前端 Vue3 Typescript 项目结构安装 Vue3 &#xff0c;使用 npm 安装 Vue接着我们创建一个 Vue 的项目&#xff0c;运行命令&…

解读Hyper-V 3.0高可用性与冗余功能

微软在提升Hyper-V 3.0高可用方面可谓不予遗力&#xff0c;添加了预测故障分析&#xff0c;增加了冗余。 IT管理员面临必须要确保网络服务器完整性与可用性的关键任务&#xff0c;这种重要性随着虚拟化越加凸显。在服务器虚拟化之前&#xff0c;服务器故障通常只会影响单个工作…

关于时序数据库

https://blog.csdn.net/ransom0512/article/details/78114167 看了一些时序数据库&#xff0c;没有太深入&#xff0c;有一些大概认识&#xff0c;记录下来。 1. 核心 数据存储分为行存储或者列存储&#xff0c;由于列存储的高压缩比&#xff0c;现在使用列存储的比较多一…

速读原著-TCP/IP(ICMP主机与网络不可达差错)

第9章 IP选路 9.3 ICMP主机与网络不可达差错 当路由器收到一份I P数据报但又不能转发时&#xff0c;就要发送一份 I C M P“主机不可达”差错报文&#xff08;I C M P主机不可达报文的格式如图 6 - 1 0所示&#xff09;。可以很容易发现&#xff0c;在我们的网络上把接在路由…

最好用的 6 款 Vue 实时消息提示通知(Message/Notification)组件推荐与测评

本文完整版&#xff1a;《最好用的 6 款 Vue 实时消息提示通知(Message/Notification)组件推荐与测评》 Vue 实时消息提示通知Vue-notification - 专注实时消息提示 各类样式随意修改 你想要的它都有SweetAlert2 - 支持 Vue 3 实时消息提示、全功能、功能应有尽有Vue-toasted -…