10

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

 2 years ago
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.
neoserver,ios ssh client

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

Wayne
前端工程师
最近更新 2022年06月02日

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

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

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

React PDFjs 搭建效果

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

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 预览功能。如果不想处理前端问题,推荐使用卡拉云,卡拉云内置各类组件,无需懂任何前端,仅需拖拽即可快速生成。

下图为使用卡拉云搭建的内部广告投放监测系统,仅需拖拽,1小时搞定。

kalacloud

再看个卡拉云的 Demo 案例,下面是用卡拉云搭建的数据库 CURD 后台管理系统,只需拖拽组件,即可在10分钟内完成搭建。

卡拉云 SQL admin 后台管理系统

可直接分享给同事一起使用:https://my.kalacloud.com/apps/8z9z3yf9fy/published

卡拉云是新一代低代码开发平台,与 React 这类框架相比,卡拉云无需配置开发环境,直接注册即可开始搭建。开发者无需处理任何前端问题,简单拖拽即可生成图表、表格、表单、富文本等功能组件,一键接入数据库及 API,快速完成企业内部工具搭建,还可以分享给团队成员共享使用,数周的开发时间,缩短至 1 小时。


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK