There was a recent feature request at work for viewing pdf files in the application. A user should be able to click on a component containing a pdf and open that pdf in a modal. In that modal the user can navigate the pages of that pdf file. In this post, I’ll show you how I was able to achieve this with relative ease thanks to a library called react-pdf-js.
最近有一项功能请求正在工作,以便在应用程序中查看pdf文件。 用户应该能够单击包含pdf的组件并以模式打开该pdf。 在该模式下,用户可以浏览该pdf文件的页面。 在本文中,我将向您展示一个名为react-pdf-js的库如何相对轻松地实现这一目标。
react-pdf-js is not to be confused with react-pdf. I tried using react-pdf first but I found it to be pain trying to configure it to work in our application. After about an hour of debugging with no luck, I decided to seek an alternative. This is when I discovered react-pdf-js.
不要将react-pdf-js与react-pdf混淆。 我首先尝试使用react-pdf,但是尝试将其配置为在我们的应用程序中工作时感到很痛苦。 经过大约一个小时的调试而没有运气,我决定寻求替代方法。 这是我发现react-pdf-js的时候。
Let’s download the library.
让我们下载库。
NPM: npm install @mikecousins/react-pdfYARN: yarn add @mikecousins/react-pdfNote: In this demo, I’ll be installing Ant Design so I can use their Modal component. react-pdf-js is NOT dependent on Ant Design in any way.
注意:在此演示中,我将安装Ant Design,以便可以使用其Modal组件。 react-pdf-js完全不依赖于Ant Design。
After installing Ant Design (npm i antd) we also need to import the Ant Design styles.
安装Ant Design( npm i antd )之后,我们还需要导入Ant Design样式。
import "antd/dist/antd.css";I’ll be importing the Modal and Button components provided by Ant Design, as well as react-pdf-js. We can import react-pdf-js as PDF. See below for what your component should look like so far.
我将导入Ant Design提供的Modal和Button组件以及react-pdf-js 。 我们可以将react-pdf-js导入为PDF 。 到目前为止,请参见下面的内容。
import React, {useState} from "react";import "antd/dist/antd.css";import '../App.css';import {Modal, Button} from "antd";import PDF from "react-pdf-js";const PdfViewer = ({pdf, onCancel, visible})=> { return( <Modal visible={visible} onCancel={onCancel} maskClosable={false} width={"50%"} > Hello World <Modal/>;};export default PdfViewer;PdfViewer accepts the props pdf, onCancel, and visible. For this demo, these props are being provided by our App component. Alternatively, you can handle all of the visibility and pdf imports in PdfViewer but that’s less dynamic. See my App component below.
PdfViewer接受道具pdf , onCancel ,并visible 。 对于此演示,这些道具由我们的App组件提供。 另外,您可以在PdfViewer处理所有可见性和pdf导入,但是动态性较差。 请参阅下面的我的App组件。
import React, {useState} from 'react';import PdfViewer from './components/PdfViewer'import {Button} from 'antd';import pdf from './assets/test.pdf'import './App.css';function App() { const [showPdf, setShowPdf] = useState(false) return ( <div className="App"> <PdfViewer pdf={pdf} onCancel={()=>setShowPdf(false)} visible={showPdf} /> <Button onClick={()=>setShowPdf(!showPdf)}> Show PdfViewer </Button> </div> );}export default App;I’m using react hooks to control whether or not to show PdfViewer. Below is a little CSS I am using to style my react app.
我正在使用react挂钩来控制是否显示PdfViewer 。 下面是一些我用来设置我的应用程序样式CSS。
.App{ width: 100%; height: 500px; display: flex; align-items: center; justify-content: center;}After adding the CSS, your app should look like a lonely button in the (almost) middle of the page. And now that we have the basic structure and modal logic down, we can get into the nitty gritty of react-pdf-js.
添加CSS之后,您的应用程序应该看起来像页面(几乎)中间的一个寂寞按钮。 现在,我们有了基本的结构和模态逻辑,我们可以深入了解react-pdf-js了。
To start, we need to set up page navigation so the user can actually change which page they want to view. We can achieve this by storing the current page as well as the total pages in functional state using useState.
首先,我们需要设置页面导航,以便用户可以实际更改他们要查看的页面。 我们可以通过使用useState存储当前page以及处于功能状态的总pages来实现这useState 。
const [page, setPage] = useState(1);const [pages, setPages] = useState(null);Now that we can keep track of the total pages and current page number, we can pass our first prop to our PDF component, page.
现在我们可以跟踪总页数和当前页数,可以将第一个属性传递给我们的PDF组件page 。
<PDF page={page}/>In addition to the current page, PDF accepts a file prop which will be the pdf passed from our App component.
除当前页面外, PDF接受file属性,即从我们的App组件传递的pdf 。
<PDF page={page} file={pdf}/>Sweet! Our PDF component can now determine which page to show for the provided pdf file. All we need now is a function for onDocumentComplete and onDocumentError. The first function gets executed when the provided file is done being processed by PDF. From this prop, we can get the total page count from the pdf, which is what we’ll use to set our pages property.
甜! 我们的PDF组件现在可以确定所提供的pdf文件显示在哪一page 。 现在我们需要的是onDocumentComplete和onDocumentError的函数。 当提供的文件由PDF处理完成时,第一个函数将执行。 通过此道具,我们可以从pdf中获取总页数,这将用于设置pages属性。
const onDocumentComplete = (numPages) =>{ setPages(numPages)}onDocumentError is a prop that handles any errors that might pop up. For now, I’m just going to console.log any errors to see what (if anything) is wrong.
onDocumentError是用于处理可能弹出的任何错误的道具。 现在,我只是要console.log任何错误,以查看错误(如果有的话)。
const onDocumentError = (err) => { console.error('pdf viewer error:', err);}Perfect! Here’s what our updated PdfViewer component should look like.
完善! 这是我们更新后的PdfViewer组件的外观。
import React, {useState} from "react";import "antd/dist/antd.css";import '../App.css';import {Modal, Button} from "antd";import PDF from "react-pdf-js";const PdfViewer = ({pdf, onCancel, visible})=> { const [page, setPage] = useState(1); const [pages, setPages] = useState(null); const onDocumentError = (err) => { console.error('pdf viewer error:', err); } const onDocumentComplete = (numPages) =>{ setPages(numPages) }return( <Modal visible={visible} onCancel={onCancel} maskClosable={false} style={{top: 20}} width={"50%"} > <PDF file={pdf} page={page} onDocumentError={onDocumentError} onDocumentComplete={onDocumentComplete} /> <p style={{textAlign: 'center'}}> Page {page} of {pages} </p> <Modal/>;};export default PdfViewer;By now, if you click the Show PdfViewer button, you should see the modal pop up with the first page of the provided pdf visible as well as the page indicator at the bottom. You also might notice that the displayed pdf is a bit left aligned. To fix this, you can add a wrapper div around PDF and apply the below styles.
现在,如果您单击Show PdfViewer按钮,您应该会看到模式弹出窗口,其中提供的pdf的第一页是可见的,底部的页面指示符是可见的。 您可能还会注意到显示的pdf有点左对齐。 要解决此问题,您可以在PDF周围添加包装器div并应用以下样式。
.pdfWrapper{ display: flex; align-items: center; justify-content: center; overflow-x: auto;}The pdf should now be nice and centered. And if your screen gets too small and the pdf overflows, the added overflow-x: auto; attribute should allow the user to scroll horizontally to see any overflowed portion of the pdf. Let’s check it out.
pdf现在应该很好并且居中。 如果屏幕太小并且pdf溢出,则添加的overflow-x: auto; 属性应允许用户水平滚动以查看pdf的任何溢出部分。 让我们来看看。
Awesome! Now let’s add some controls so the user can see the other pages. We can do this by adding a footer to our modal, and in that footer prop make a div that wraps two Ant Design Button components. One for Previous, and one for Next. See the footer element below.
太棒了! 现在让我们添加一些控件,以便用户可以看到其他页面。 我们可以通过在模型中添加footer来实现此目的,并在footer道具中创建一个div ,该div封装了两个Ant Design Button组件。 一个用于上一个,另一个用于下一个。 请参见下面的页脚元素。
const footer = <div className="footer"> <Button onClick={()=>onPage(0)}>Previous</Button> <Button onClick={()=>onPage(1)}>Next</Button></div>Our footer class will have the style attributes below.
我们的footer类将具有以下样式属性。
.footer{ display: flex; justify-content: space-between;}Now we can include footer={footer} as a prop for our Modal. You should now see two buttons at the bottom of the Modal. However, these buttons don’t do anything yet because we need to define our onPage function. This function will act as the navigator for both previous and next functionality. We pass a 0 for a previous action, and a 1 for a next action. This can allow us to determine which direction to go in regards to setting the value for page.
现在我们可以将footer={footer}包含在Modal 。 现在,您应该在Modal的底部看到两个按钮。 但是,这些按钮尚无任何作用,因为我们需要定义onPage函数。 此功能将充当上一个和下一个功能的导航器。 我们为上一个动作传递0为下一个动作传递1 。 这可以让我们确定关于设置page值的方向。
const onPage = (type) =>{ var newPage = type ? page + 1 : page - 1 setPage(newPage)}This is good, but what happens if we exceed our pages?
很好,但是如果我们超过页面数会怎样?
That’s not good! I’m not sure why the creator of this library didn’t build some default behavior to handle this logic but no worries! We can create our own.
这不好! 我不确定为什么该库的创建者没有建立一些默认行为来处理这种逻辑,但是不用担心! 我们可以创建自己的。
if (newPage > pages){ newPage = 1} else if (newPage < 1){ newPage = pages}This logic is resetting the value of newPage before we officially set the value for page. If we go too far next (newPage === pages + 1), we set newPage to 1, effectively looping back to the first page of the pdf. If we go too far back (newPage === 0) then we set newPage equal to the total number of pages, looping back to the end. This way we can cycle fully and correctly through the pdf no matter how many times a user clicks Next or Previous. Our final onPage should look like the following.
在我们正式设置page的值之前,此逻辑将重置newPage的值。 如果下一个newPage === pages + 1太远( newPage === pages + 1 ),我们newPage设置为1,从而有效地循环回到pdf的第一页。 如果我们走得太远( newPage === 0 ),则将newPage设置newPage等于总pages ,然后循环回到末尾。 这样,无论用户单击“下一个”或“上一个”多少次,我们都可以完全正确地浏览pdf。 我们最终的onPage应该如下所示。
const onPage = (type) =>{ var newPage = type ? page + 1 : page - 1 if (newPage > pages){ newPage = 1 } else if (newPage < 1){ newPage = pages } setPage(newPage)}Now, let’s see our PDF viewer in action.
现在,让我们来看一下我们的PDF查看器。
It’s looking pretty good! The user can now effectively cycle through the pages of the pdf. While this is what the original feature called for, we can take things a step further.
看起来不错! 用户现在可以有效地浏览pdf页面。 虽然这是原始功能所要求的,但我们可以将其进一步发展。
Another prop that we can pass to PDF is scale. With scale, we can manage how large our pdf file is to scale. By default scale is 1. First, let’s make a new state value for scale.
我们可以传递给PDF另一个道具是scale 。 使用scale ,我们可以管理pdf文件的大小。 默认情况下, scale为1。首先,让我们为scale创建一个新的状态值。
const [scale, setScale] = useState(1); //default to 1Next, let’s make a few changes to our footer. Our footer will now include tools to allow the user to zoom in up to 200% (true scale of 2) or down to 10% (true scale of 0.1). Let’s import ZoomOutOutlined and ZoomInOutlined from Ant Design. These will be the icons the user clicks in order to zoom in or out.
接下来,让我们对页脚进行一些更改。 现在,我们的页脚将包含一些工具,使用户可以放大至200%(真实比例为2)或缩小至10%(真实比例为0.1)。 让我们从Ant Design导入ZoomOutOutlined和ZoomInOutlined 。 这些将是用户单击以放大或缩小的图标。
import {ZoomInOutlined, ZoomOutOutlined} from '@ant-design/icons';Now that they’re imported, let’s start constructing our new footer.
现在已经导入了它们,让我们开始构建新的页脚。
const zoomStyle = { marginLeft: 10, cursor: 'pointer'}const footer = <div className="footer"> <Button onClick={()=>onPage(0)}>Previous</Button> <div> <span style={{textAlign: 'center'}}>Page {page} of {pages}</span <ZoomOutOutlined style={zoomStyle} onClick={()=>onSetScale(0)}/> <ZoomInOutlined style={zoomStyle} onClick={()=>onSetScale(1)}/> <span>{Math.round(scale * 100)}%</span> </div> <Button onClick={()=>onPage(1)}>Next</Button></div>As you can see, we also moved the page indicator into the footer too. This is because we’re going to give the Modal the bodyStyle prop to give it a fixed height and allow any vertically overflowed content to be scrollable.
如您所见,我们还将页面指示器也移到了页脚中。 这是因为我们将为Modal提供bodyStyle道具,以使其具有固定高度,并使任何垂直溢出的内容都可滚动。
bodyStyle={{height: 600, overflowY: 'auto'}}I originally tried this on the pdfWrapper but for some reason if the scale got too high it would still cut off some of the pdf page. Now that we have our Modal and footer reconfigured to support increasing scale, let’s move on to our onSetScale function.
我最初在pdfWrapper上尝试过此操作,但由于某种原因,如果比例过高,它仍会切断部分pdf页面。 现在我们已经重新配置了Modal和footer以支持增加的比例,让我们继续进行onSetScale函数。
const onSetScale = (type) =>{ var newScale = type ? scale + 0.1 : scale - 0.1; if (newScale > 2){ newScale = 2 } else if (newScale < 0.1){ newScale = 0.1 } setScale(newScale)}It’s pretty similar to our onPage function. I was originally trying to consolidate the two but since their end logic is a bit different I figured it was simpler to just keep their functions separate. Now that we have our scale UI and functionality in place, let’s see our final product.
它与我们的onPage函数非常相似。 我最初试图合并两者,但是由于它们的最终逻辑有些不同,所以我认为仅将它们的功能分开是比较简单的。 现在我们已经有了缩放UI和功能,让我们看看我们的最终产品。
And there you have it. A relatively simple yet robust implementation of react-pdf-js to allow users to open, navigate and zoom in/out of a pdf. All accompanying code to this demo can be found on GitHub.
那里有。 react-pdf-js一种相对简单但健壮的实现,允许用户打开,导航和放大/缩小pdf。 该演示的所有随附代码都可以在GitHub上找到。
翻译自: https://levelup.gitconnected.com/how-to-view-pdfs-in-a-react-app-32a9f3c48f06
