RELEASE: What's New in Summer 2024

How To Build a Customizable React PDF Viewer?

By Andrey Safonov | 2023 Jun 16

Sanity Image
Read time

5 min

In this blog, we will cover how to build a customizable and feature-rich PDF Viewer in React. We will be using the Apryse SDK - Apryse’s rendering engine - and building a custom UI to display PDFs, scroll pages, zoom, and search with annotation and highlighting support.

We’ll assume that you already have a Node environment setup and you have a React app up and running. If you do not, you can grab the source code to this blog on our GitHub https://github.com/PDFTron/webviewer-custom-ui. You can also just create a new React app after setting up a Node environment, by running:

npx create-react-app my-app
cd my-app

For more info on create-react-app refer to the official documentation.

Or if you prefer using Vite, then you can follow the instructions at the start of this video.


OK! Let’s get started creating our PDF rendering app!

Installing a PDF Rendering Engine

First, we need a robust rendering engine that will display PDFs. There are several open-source rendering engines available such as PDF.js and PDFium, however in this article we will use Apryse’s proprietary engine since it offers so much functionality.

Why is Apryse SDK such a powerful tool?

Copied to clipboard

Apryse’s proprietary engine comes with a lot of functionality, straight out-of-the-box. In addition to being great at rendering PDFs, it also supports:

Almost all functionality is performed directly within the browser (using WASM), making it extremely fast and keeping your documents secure rather than them being transferred over the internet and processed on a remote server.

Installing the Apryse Rendering Engine

Copied to clipboard

Probably the easiest way to get the Apryse rendering engine is to run:

npm i @pdftron/webviewer

Once the package has been installed, copy the `public/core` contents located in your `node_modules/@pdftron/webviewer/public/core` to the `/public/webviewer` folder in your React app.

Now, you can reference the necessary scripts for rendering between the <body> tags of `public/index.html`, similarly to how we did it in our GitHub repository for this project: https://github.com/PDFTron/webviewer-custom-ui/blob/master/public/index.html#L29


<script src="%PUBLIC_URL%/webviewer/core/webviewer-core.min.js"></script>

<script src="%PUBLIC_URL%/webviewer/core/pdf/PDFNet.js"></script>


Now, you should be able to reference it wherever you are building the PDF Viewer.


Creating a React PDF Viewer Component

You can now create a reusable PDF Viewer component in your React app. However, in our example, we will keep things simple and just add the code directly to `App.js`.

Let’s add where the PDF will be rendered. To begin, let’s add a couple of references that we will need inside the component or App.js:


const App = () => {
const viewer = useRef(null);
const scrollView = useRef(null);

In the return method for App.js or your component, add the following:


return (
  <div className="App">
    <div id="scroll-view" ref={scrollView}></div>
    <div id="viewer" ref={viewer}></div>
  </div>
  )

And then insert a `useEffect` in App.js (which should only run once upon rendering the component), and add the following:


const App = () => {
  const viewer = useRef(null);
  const scrollView = useRef(null);
  const [documentViewer, setDocumentViewer] = useState(null);
  const [annotationManager, setAnnotationManager] = useState(null);
  useEffect(() => {
    const Core = window.Core;
    Core.setWorkerPath('/webviewer/core');  
    Core.enableFullPDF();
    const documentViewer = new Core.DocumentViewer();
    documentViewer.setScrollViewElement(scrollView.current);
    documentViewer.setViewerElement(viewer.current);
    documentViewer.enableAnnotations();
    documentViewer.loadDocument(' https://pdftron.s3.amazonaws.com/downloads/pl/demo-annotated.pdf');

    setDocumentViewer(documentViewer);
    documentViewer.addEventListener('documentLoaded', () => {
      setAnnotationManager(documentViewer.getAnnotationManager());
    });
  }, []);


Okay, what is happening there?

  1. We load the Core rendering engine from Apryse.
  2. We set the path to the resources necessary for document rendering.
  3. We create a new document viewer and point it to the div element we created earlier.
  4. We then enable annotations and load our PDF from an s3 bucket, however, you can point it to local files as well in `public` folder.
  5. After that, we set the document viewer to the one we created.
  6. And then we wait for an event that lets us know the viewer is ready to go and the document is loaded.

Provide a best-in-class viewer experience, best quality and performance across web, mobile, and desktop applications with Apryse.

Now, let’s add some CSS, to make sure the viewer looks its best. Inside of App.css (or your component’s CSS), add the following and do not forget to import it:


#scroll-view {
bottom: 0;
height: 100%;
width: 100%;
overflow: auto;
}
#viewer {
margin: auto;

}

At this point, we are ready to start our React app and see the PDF render:


npm run start

If you ran into any trouble, or something is going wrong, just refer to our GitHub sample. If you are still stuck, chat with us on Discord using the link at the end of this article.

If you are looking for a pre-built UI that is battle-tested by millions of developers, check out our other blog: https://apryse.com/blog/embed-react-pdf-viewer.

Add Annotation Support to Your PDF Viewer

Copied to clipboard

The next step, in this demo, is to allow users to draw annotations and comment on a PDF. Thanks to the Apryse Core rendering engine, WebViewer comes with out-of-the-box support for all annotation types supported in Adobe Reader. So, in this section, we are going to add a button to the UI that allows us to draw rectangles on a PDF document.

Inside the return method for App.js or your component, add the button tag just before the scroll view:

return (
  <div className="App">
    <button onClick={createRectangle}>Rectangle</button>
    <div id="scroll-view" ref={scrollView}></div>
    <div id="viewer" ref={viewer}></div>
  </div>
)

Let’s now add the code that will set the tool to be a rectangle whenever that button is pressed.

Somewhere between useEffect and the return statement, add the following:

const createRectangle = () => {
  documentViewer.setToolMode(documentViewer.getTool(window.Core.Tools.ToolNames.RECTANGLE));
};

Now, the button press will allow users to add rectangle annotations. Restart the app and try it yourself. We have used very basic CSS so feel free to play around with it to get it to match your overall theme.

Next Steps

At this point, you can add more tool support, page manipulation, search, redaction and more. Check out the full sample available on GitHub and refer to our guides for more information. You can also chat with us on our Discord.

This article was updated in March 2024.

Sanity Image

Andrey Safonov

Director of Product

LinkedIn link

Share this post

email
linkedIn
twitter