COMING SOON: Fall 2024 Release

How to Build a jQuery PDF Viewer with PDF.js

By Dustin Riley | 2020 Jul 15

Sanity Image
Read time

4 min

In this article (a four-minute read), you’ll learn how to quickly build a PDF viewer with jQuery and PDF.js, a popular open-source PDF viewer.

Prerequisites

Step 1 - Install http-server

Copied to clipboard

We will be using http-server to run our website locally. To install http-server, run the following command from the Node.js command line:

npm install --global http-server

Step 2 - Create the Project Directory

Copied to clipboard

Now we will create a simple project directory for our jQuery project. In your project folder create a folder structure that looks something like this:

pdfjs-jquery-project
├── public
│    ├── js
│    │   ├── index.js
│    ├── css
│    │   ├── index.css
│    ├── index.html
│    ├── lib
│    │   ├── ...
│    └── ...
└── ...

All these files will be empty for now. We will populate them with content shortly.

Step 3 - Implementing PDF.js

Copied to clipboard

We will now integrate the open-source PDF.js library into our project to render a PDF inside our app. We will start by downloading the latest stable release from GitHub and then extracting the contents into the public/lib folder.

We will also need a PDF file to view, which we will place in the web folder. You can use your own or download one from here.

The new file structure in our project folder will look like the following:

pdfjs-jquery-project
├── public
│    ├── js
│    │   ├── index.js
│    ├── css
│    │   ├── index.css
│    ├── index.html
│    ├── lib
│    │   ├── ui
│    │   │   ├── ...
│    │   ├── core
│    │   │   ├── ...
│    │   └── webviewer.min.js
│    └── ...
└── ...

Step 4 - Create the Project

Copied to clipboard

Next, we will make it so we can view the default viewer within our project. In the public/index.html add the following:

<!DOCTYPE html>
<html lang="en">

<head>
  <title>jQuery PDF.js Viewer</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>

<body>
  <div style={{"height":"100vh"}}>
    <iframe height="100%" width=100% src='lib/web/viewer.html'></iframe>
  </div>
</body>

</html>

Now navigate to the project directory in the Node.js terminal and run the following command:

http-server

Navigate to http://localhost:8080/ and you’ll see our default viewer:

Blog image

To change the default PDF, add your custom PDF to lib/web folder and change:

<iframe height="100%" width=100% src='lib/web/viewer.html'></iframe>

With:

<iframe height="100%" width=100% src='lib/web/viewer.html?file=<YOUR-FILE.pdf>'></iframe>

Replacing <YOUR-FILE.pdf> with the name of your PDF file.

Step 5 - Customizing the PDF.js Toolbar with jQuery

Copied to clipboard

As a final step, we will reorganize the toolbar by moving elements around, removing buttons, and changing the icons.

Let’s open public/lib/web/viewer.html and add the following to the <head> section:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="customToolbar.js"></script>

Next, we’ll create customToolbar.js inside the public/lib/web folder and add the following code:

let sheet = (function() {
  let style = document.createElement("style");
  style.appendChild(document.createTextNode(""));
  document.head.appendChild(style);
  return style.sheet;
 })();
 function editToolBar(){
  //when the page is resized, the viewer hides and move some buttons around.
  //this function forcibly show all buttons so none of them disappear or re-appear on page resize
  removeGrowRules();

  /* Reorganizing the UI */
  // the 'addElemFromSecondaryToPrimary' function moves items from the secondary nav into the primary nav
  // there are 3 primary nav regions (toolbarViewerLeft, toolbarViewerMiddle, toolbarViewerRight)

  //adding elements to left part of toolbar
  addElemFromSecondaryToPrimary('pageRotateCcw', 'toolbarViewerLeft')
  addElemFromSecondaryToPrimary('pageRotateCw', 'toolbarViewerLeft')
  addElemFromSecondaryToPrimary('zoomIn', 'toolbarViewerLeft')
  addElemFromSecondaryToPrimary('zoomOut', 'toolbarViewerLeft')
  
  //adding elements to middle part of toolbar
  addElemFromSecondaryToPrimary('previous', 'toolbarViewerMiddle')
  addElemFromSecondaryToPrimary('pageNumber', 'toolbarViewerMiddle')
  addElemFromSecondaryToPrimary('numPages', 'toolbarViewerMiddle')
  addElemFromSecondaryToPrimary('next', 'toolbarViewerMiddle')
  
  //adding elements to right part of toolbar
  addElemFromSecondaryToPrimary('secondaryOpenFile', 'toolbarViewerRight')
  
  /* Changing icons */
  changeIcon('previous', 'icons/baseline-navigate_before-24px.svg')
  changeIcon('next', 'icons/baseline-navigate_next-24px.svg')
  changeIcon('pageRotateCcw', 'icons/baseline-rotate_left-24px.svg')
  changeIcon('pageRotateCw', 'icons/baseline-rotate_right-24px.svg')
  changeIcon('viewFind', 'icons/baseline-search-24px.svg');
  changeIcon('zoomOut', 'icons/baseline-zoom_out-24px.svg')
  changeIcon('zoomIn', 'icons/baseline-zoom_in-24px.svg')
  changeIcon('sidebarToggle', 'icons/baseline-toc-24px.svg')
  changeIcon('secondaryOpenFile', './icons/baseline-open_in_browser-24px.svg')

  /* Hiding elements */
  removeElement('secondaryToolbarToggle')
  removeElement('scaleSelectContainer')
  removeElement('presentationMode')
  removeElement('openFile')
  removeElement('print')
  removeElement('download')
  removeElement('viewBookmark')
 }
function changeIcon(elemID, iconUrl){
    let element = $('#'+elemID)
    let classNames = element[0].className;
    classNames = elemID.includes('Toggle')? 'toolbarButton#'+elemID : classNames.split(' ').join('.');
    classNames = elemID.includes('view')?  '#'+elemID+'.toolbarButton' : '.'+classNames
    classNames+= "::before";
    addCSSRule(sheet, classNames, `content: url(${iconUrl}) !important`, 0)
}
function addElemFromSecondaryToPrimary(elemID, parentID){
    let element = $('#'+elemID);
    let parent = $('#'+parentID);
    $(element).css('min-width', '0px');
    $(element).empty();
    $(parent).append(element);
}
function removeElement(elemID){
    $('#'+elemID).remove();
}
function removeGrowRules(){
    addCSSRule(sheet, '.hiddenSmallView *', 'display:block !important');
    addCSSRule(sheet, '.hiddenMediumView', 'display:block !important');
    addCSSRule(sheet, '.hiddenLargeView', 'display:block !important');
    addCSSRule(sheet, '.visibleSmallView', 'display:block !important');
    addCSSRule(sheet, '.visibleMediumView', 'display:block !important');
    addCSSRule(sheet, '.visibleLargeView', 'display:block !important');
}
function addCSSRule(sheet, selector, rules, index) {
	if("insertRule" in sheet) {
		sheet.insertRule(selector + "{" + rules + "}", index);
	}
	else if("addRule" in sheet) {
		sheet.addRule(selector, rules, index);
	}
}
$(document).ready(editToolBar);

The PDF.js primary toolbar is broken down into 3 regions:

Blog image

The secondary toolbar is accessed via the chevron icon in the right region:

Blog image

We can move elements from the secondary toolbar into the left, middle, or right regions of the primary toolbar with the addElemFromSecondaryToPrimary function in customToolbar.js. For example, uncommenting this line will move the counter-clockwise rotation tool to the left region of the primary toolbar:

addElemFromSecondaryToPrimary('pageRotateCcw', 'toolbarViewerLeft')

If you wanted to move pageRotateCcw to the middle region instead, you’d replace toolbarViewerLeft with toolbarViewerMiddle, or toolbarViewerRight for the right region. To move a different tool, replace the pageRotateCcw ID with the element ID you want to move. (See below for a full list of element IDs.)

We can also hide elements like this:

removeElement('print')
removeElement('download')

To hide different elements, replace print or download with the element ID.

NOTE: Hiding the download and print buttons is not a bulletproof way to protect our PDF, because it’s still possible to look at the source code to find the file. It just makes it a bit harder.

We can also customize the icons for various tools by swapping out the SVG file, like this:

changeIcon('previous', 'icons/baseline-navigate_before-24px.svg')

In the above example, previous is the element ID, while icons/baseline-navigate_before-24px.svg is the path to the tool icon.

And that’s it!

Conclusion

Copied to clipboard

As you can see, rendering a PDF with jQuery isn't difficult using open-source libraries.

Building a PDF Viewer with jQuery is relatively straightforward, but once you want to start annotating, signing, or filling forms, you would have to implement these things yourself. See our PDF.js Build vs Buy to learn more.

That’s where PDF.js Express comes in. It’s a commercial PDF.js viewer that wraps a React-based UI around the open-source PDF.js rendering engine and offers out-of-the-box features like annotations, form filling and e-signatures. It’s fully compatible with many frameworks -- check out the demo, and let us know what you think!

If you need high-fidelity rendering, increased reliability, and faster performance, you could consider Apryse WebViewer. It’s a js PDF library that integrates with many frameworks and offers hundreds of features, like redaction, editing, page manipulation, real-time document collaboration, digital signatures, and much more. Check out the WebViewer demo.

If you have any questions about implementing PDF.js Express in your project, please contact us and we will be happy to help!

Sanity Image

Dustin Riley

Share this post

email
linkedIn
twitter