React-pdf 实现 PDF 文件在线预览

13,225 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

概述

PDF 文档的预览功能在日常项目中很常见,今天我们就是使用 Reactjs + React-PDF 实现一个 PDF 文档在线预览功能,先来看看实现后的效果

iShot_2022-06-03_12.33.25.gif

PDF 文档在线预览实现的功能有

  • PDF 文档预览功能
  • 上下翻页功能
  • 输入页面跳转功能
  • PDF 文档放大缩小功能

功能实现

项目初始化

项目使用 Reactjs + React-PDF 实现,所以我们先来初始化一 React 个项目。

终端中输入命令 pnpm create vite reactjs-pdf-preview -- --template react 进行项目的初始化,接着我们 cd 进入项目根目录,执行命令 pnpm install 安装依赖包, 依赖包安装完成后,我们可以先启动项目,看看项目是否运行正常,输入命令 pnpm run dev

  vite v2.9.9 dev server running at:

  > Local: http://localhost:3000/
  > Network: use `--host` to expose

  ready in 468ms.
  

可以看到在终端中输出了如上内容,在浏览器地址输入上述地址,可以看到初次运行的页面,说明项目启动正常,接着我们来添加插件 React-PDF

终端中输入

npm install react-pdf 
or
yarn add react-pdf
or 
pnpm install react-pdf

插件安装完毕后,我们在 src 目录下新建一个文件

src/components/pdfPreview.js

文件建好后,我们添加一个预览 PDF 的类,如下内容

import React, { PureComponent } from 'react';
export default class PdfPreview extends PureComponent {
    
    render(){
         return (
             <div className="pdf-view"></div>
         )
    }
}

接着我们引入 PDF 预览插件的内容

import React, { PureComponent } from 'react';
import styles from './file.css';

import { Document, Page, pdfjs } from "react-pdf";
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;

export default class PdfPreview extends PureComponent {

    render () {
        const { prfUrl } = this.props
        return (
            <div className="pdf-view">
                   <Document
                        file={prfUrl}
                        onLoadSuccess={this.onDocumentLoadSuccess}
                        loading={'加载中...'}
                    >
                        <Page pageNumber={pageNumber} width={pageWidth} loading={'加载中...'} />
                    </Document>
            </div>
        );
    }
}

PDF 文件由外部传入,可以作为一个公共组件在需要的地方引用

我们在 App.jsx 文件中引入,进行组件的测试,

import PdfPreview from "./components/pdf"
import pdf from "./Javascript.pdf"
import './App.css'

function App() {

  return (
    <div className="App">
      <PdfPreview prfUrl={pdf} />
    </div>
  )
}

export default App

可以看到 PDF 加载成功,但是做到这样还仅仅是不够的,我们下一步对他进行优化下,添加翻页按钮,放大缩小按钮和页码跳转功能,并对展示的样式进行优化

截屏2022-06-02 下午11.46.35.png

样式优化

我们添加如下代码,并新建一个 css 文件,用来存储我们的样式文件,

<div className="pdf-view">
    <div className="container">
        <Document file={prfUrl} > </Document>
    </div>
    <div className="page-tool">
        <div className='page-tool-item'> 上一页</div>
        <div className='page-tool-item'> 下一页</div>
        <div className="input"> <input type="number" /> / </div>
        <div className='page-tool-item'> 放大</div>
        <div className='page-tool-item' > 缩小</div>
    </div>
</div>
 
.pdf-view {
    display: flex;
    justify-content: center;
    padding: 30px 0 50px;
    overflow: hidden;
    box-sizing: border-box;
    height: 100vh;
    position: relative;
}

.container {
    box-shadow: rgb(0 0 0 / 20%) 0px 2px 4px 0px;
    width: max-content;
    margin: 0 auto;
    overflow: scroll;
    box-sizing: border-box;
}

.page-tool {
    position: absolute;
    bottom: 35px;
    padding-left: 15px;
    padding-right: 15px;
    display: flex;
    align-items: center;
    background: rgb(66, 66, 66);
    color: white;
    border-radius: 19px;
}

.page-tool-item {
    padding: 8px 15px;
    padding-left: 10px;
    cursor: pointer;
}

input {
    display: inline-block;
    width: 50px;
    text-align: center;
    margin-right: 10px;
    height: 24px;
}

input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
    -webkit-appearance: none;
}

input[type="number"] {
    -moz-appearance: textfield;
}

样式添加完后,可以看到页面是这样的

截屏2022-06-03 下午12.37.05.png

我们接下来继续完善 PDF 预览功能

功能完善

我们在 state 中分别定义如下变量

 pageNumber: 1,
 pageNumberInput: 1,
 pageNumberFocus: false,
 numPages: 1,
 pageWidth: 503,
 fullscreen: false
  • pageNumber: 1 //页码
  • pageNumberInput: 1 // input 显示的值
  • pageNumberFocus: false // 是否获得焦点
  • numPages: 1 // 总页数
  • pageWidth: 503 // 预览pdf的宽度
  • fullscreen: false // 是否全屏

这里我们分别定义如下函数,用来实现上下翻页,放大缩小,窗口全屏,以及页码跳转功能

  lastPage = () => {
        if (this.state.pageNumber == 1) {
            return;
        }
        const page = this.state.pageNumber - 1;
        this.setState({ pageNumber: page, pageNumberInput: page });
    };
    nextPage = () => {
        if (this.state.pageNumber == this.state.numPages) {
            return;
        }
        const page = this.state.pageNumber + 1;
        this.setState({ pageNumber: page, pageNumberInput: page });
    };
    onPageNumberFocus = (e) => {
        this.setState({ pageNumberFocus: true });
    };
    onPageNumberBlur = (e) => {
        this.setState({
            pageNumberFocus: false,
            pageNumberInput: this.state.pageNumber,
        });
    };
    onPageNumberChange = (e) => {
        let value = e.target.value;
        value = value <= 0 ? 1 : value;
        value = value >= this.state.numPages ? this.state.numPages : value;
        this.setState({ pageNumberInput: value });
    };
    toPage = (e) => {
        if (e.keyCode === 13) {
            this.setState({ pageNumber: Number(e.target.value) });
        }
    };

    pageZoomOut = () => {
        if (this.state.pageWidth <= 503) {
            return;
        }
        const pageWidth = this.state.pageWidth * 0.8;
        this.setState({ pageWidth: pageWidth });
    };
    pageZoomIn = () => {
        const pageWidth = this.state.pageWidth * 1.2;
        this.setState({ pageWidth: pageWidth });
    };

    pageFullscreen = () => {
        if (this.state.fullscreen) {
            this.setState({ fullscreen: false, pageWidth: 600 });
        } else {
            this.setState({ fullscreen: true, pageWidth: window.screen.width - 40 });
        }
    };

这里要说的是 toPage 函数,需要判断下 keyCode 键盘事件作下处理

然后分别将事件绑定到按钮上,绑定时间后哦的代码如下

<div className="page-tool">
                    <div className="page-tool-item" onClick={this.lastPage}>
                        {" "}
                        上一页
                    </div>
                    <div className="page-tool-item" onClick={this.nextPage}>
                        {" "}
                        下一页
                    </div>
                    <div className="input">
                        <input
                            value={pageNumberFocus ? pageNumberInput : pageNumber}
                            onFocus={this.onPageNumberFocus}
                            onBlur={this.onPageNumberBlur}
                            onChange={this.onPageNumberChange}
                            onKeyDown={this.toPage}
                            type="number"
                        />{" "}
                        / {numPages}
                    </div>
                    <div className="page-tool-item" onClick={this.pageZoomIn}>
                        {" "}
                        放大
                    </div>
                    <div className="page-tool-item" onClick={this.pageZoomOut}>
                        {" "}
                        缩小
                    </div>
                    <div className="page-tool-item" onClick={this.pageFullscreen}>
                        {fullscreen ? "恢复默认" : "适合窗口"}
                    </div>
                </div>

事件绑定后我们测试发现,初始加载时显示的不够友好,这里我们优化一下,使用 React-PDF 的 家长事件 onLoadSuccess 添加一个函数,PDF 在加载完成后会调用这个函数,我们在这个函数中初始化页码

   onDocumentLoadSuccess = ({ numPages }) => {
        this.setState({ numPages: numPages });
    };

另外添加 loading 状态,这里可以使用加载动画,也可以使用文字

  <div className="container">
    <Document
        file={prfUrl}
        onLoadSuccess={this.onDocumentLoadSuccess}
        loading={"加载中..."}
    >
        <Page
            pageNumber={pageNumber}
            width={pageWidth}
            loading={"加载中..."}
        />
    </Document>
</div>
                

写到这里,PDF 文件预览组件我们已经写的差不多了,当然传入的文件不一定 PDF 文件地址,也可以是 base64 格式的内容,

如果预览地址是线上地址,需要带 cookie 的,可以在 state 中设置 然后传递进去

{ url: 'http://example.com/sample.pdf', httpHeaders: { 'X-CustomHeader': '40359820958024350238508234' }, withCredentials: true }

Reat-PDF Github