Available Now: Explore our latest release with enhanced accessibility and powerful IDP features

Building a Custom JavaScript PDF Viewer: PDF.js vs Apryse WebViewer

By Garry Klooesterman | 2024 Dec 06

Sanity Image
Read time

6 min

Summary: Building a custom JavaScript PDF viewer from scratch may involve extra steps when compared to other solutions, but it does give you more control to customize its features, appearance, and performance to meet the specific needs of your project. In this blog, you’ll learn how to build a basic PDF viewer using PDF.js and Apryse WebViewer.

Introduction

Copied to clipboard

Ever feel constrained by the limitations of existing PDF libraries? Or maybe you’re intrigued by the challenge of creating something from scratch. In this blog, we’ll explore building a custom JavaScript PDF viewer using PDF.js, an open-source solution, and Apryse WebViewer, a commercial solution. Whether you are an early career beginner or a seasoned developer, creating a custom PDF viewing experience gives you the freedom to tailor its features, appearance, and performance to your specific needs.

PDF.js

Copied to clipboard

How to create a JavaScript PDF viewer using PDF.js

1. Create your index.html page to set up the JavaScript PDF viewer and add the following code:

<!DOCTYPE html> 
<html> 
    <head> 
      <title>PDF Viewer</title> 
      <meta charset="UTF-8" /> 
    </head> 
    <body> 
      <div id="app"> 
        <div role="toolbar" id="toolbar"> 
          <div id="pager"> 
            <button data-pager="prev">prev</button> 
            <button data-pager="next">next</button> 
          </div> 
          <div id="page-mode"> 
            <label>Page Mode 
              <input type="number" value="1" min="1" /> 
            </label> 
          </div> 
        </div> 
        <div id="viewport-container"> 
          <div role="main" id="viewport"></div> 
        </div> 
      </div> 
      <!-- Load PDF.js library --> 
      <script src="https://unpkg.com/pdfjs-dist@latest/build/pdf.min.mjs" type="module"></script> 
      <script src="index.js" type="module"></script> 
    </body> 
</html> 

2. Next, create a JavaScript file called index.js for the functions we’ll use in the viewer. From this point on, we’ll be adding the code to this file and we’ll look at what each part does as we go.

3. Add the following code to the index.js file to import the PDF.js library.

import * as pdfjsLib from 'https://unpkg.com/pdfjs-dist@latest/build/pdf.min.mjs';
// Setting worker src for PDF.js.
pdfjsLib.GlobalWorkerOptions.workerSrc =
'https://unpkg.com/pdfjs-dist@latest/build/pdf.worker.min.mjs';

4. Define the following variables to manage the PDF file.

let currentPageIndex = 0;
let pageMode = 1; // Determines the number of pages shown at a time.
let cursorIndex = Math.floor(currentPageIndex / pageMode);
let pdfInstance = null; // Holds the PDF document instance.
let totalPagesCount = 0; // Total number of pages in the PDF.
const viewport = document.querySelector('#viewport');

5. Add this function to load, initialize, and render the PDF. We’ll call the function at the end of the index.js file to load a PDF.

window.initPDFViewer = function (pdfURL) {
  pdfjsLib.getDocument(pdfURL).promise.then((pdf) => {
  pdfInstance = pdf;
  totalPagesCount = pdf.numPages;
  initPager(); // Initializes the page navigation.
  initPageMode(); // Initializes the page mode (single or multiple pages).
  render(); // Renders the initial page.
  });
};

6. We’ll now add buttons to go back and forth through the PDF using event listeners with this code:

function onPagerButtonsClick(event) {
  const action = event.target.getAttribute('data-pager');
  if (action === 'prev') {
    if (currentPageIndex === 0) return;
    currentPageIndex -= pageMode;
    if (currentPageIndex < 0) currentPageIndex = 0;
    render();
  }
  if (action === 'next') {
    if (currentPageIndex === totalPagesCount - 1) return;
    currentPageIndex += pageMode;
    if (currentPageIndex > totalPagesCount - 1)
    currentPageIndex = totalPagesCount - 1;
    render();
  }
}

7. Set up the option to switch between single-page and multi-page view with this function to handle changes to pageMode.

function onPageModeChange(event) {
  pageMode = Number(event.target.value);
  render();
}
function initPageMode() {
  const input = document.querySelector('#page-mode input');
  input.setAttribute('max', totalPagesCount);
  input.addEventListener('change', onPageModeChange);
}

8. The render function uses pdfInstance.getPage() to render the pages based on the pageMode and currentPageIndex values. Each page is displayed in a <div> container with a canvas element. Add this code to the index.js file:

function render() {
  cursorIndex = Math.floor(currentPageIndex / pageMode);
  const startPageIndex = cursorIndex * pageMode;
  const endPageIndex =
    startPageIndex + pageMode < totalPagesCount
    ? startPageIndex + pageMode - 1
    : totalPagesCount - 1;
  const renderPagesPromises = [];
  for (let i = startPageIndex; i <= endPageIndex; i++) {
    renderPagesPromises.push(pdfInstance.getPage(i + 1));
  }
  Promise.all(renderPagesPromises).then((pages) => {
    const pagesHTML = pages
    .map(
      () =>
        `<div style="width: ${
          pageMode > 1 ? '50%' : '100%'
        }"><canvas></canvas></div>`,
      )
    .join('');
  viewport.innerHTML = pagesHTML;
  pages.forEach((page, index) => renderPage(page, index));
 });
}

9. Now, we’re ready to add the last function. This function renders each page to its own canvas element and adjusts the scale to fit the container width.

function renderPage(page, pageIndex) {
  let pdfViewport = page.getViewport({ scale: 1 });
  const container = viewport.children[pageIndex]; // Correctly map the page to its container.
  if (!container) {
    console.error('Container not found for page ' + pageIndex);
  return;
  }
 const canvas = container.querySelector('canvas');
 const context = canvas.getContext('2d');
 pdfViewport = page.getViewport({
  scale: container.offsetWidth / pdfViewport.width,
 });
 canvas.height = pdfViewport.height;
 canvas.width = pdfViewport.width;
 page.render({
  canvasContext: context,
  viewport: pdfViewport,
 });
}

10. The last thing we need to do is call the initPDFViewer function with the path of the PDF we want to render using this code:

// Initialize the PDF viewer with the example file.
initPDFViewer('assets/example.pdf');

Remember to specify the path and name of the PDF you want to load.

11. Load the index.html webpage and you should see your PDF.

And with a few easy steps, you’ve created a basic PDF viewer using JavaScript and PDF.js.

Apryse WebViewer

Copied to clipboard

For basic viewing of small and simple PDFs, an open-source option, such as PDF.js, may be cost-effective, but does have its drawbacks. A commercial option, such as Apryse WebViewer, offers more complex features and delivers a more enriching experience.

WebViewer is a JavaScript Document SDK compatible with all frameworks and browsers and is fully featured right out of the box as a single, customizable component, allowing you to view, create, search, annotate, and edit PDFs directly within a browser.

1. Download WebViewer and extract the .zip file to your project directory.

2. Create an index.html webpage and add this code:

<!DOCTYPE html>
<html style="height:100%;">
  <head>
    <meta http-equiv="Content-Type" content="text/html">
    <script src="WebViewer/lib/webviewer.min.js"></script>
  </head>
  <body style="width:100%; height:100%; margin:0px; padding:0px; overflow:hidden">
    <div id="viewer" style="height: 90%; overflow: hidden;"></div>
  </body>
</html>

3. Next, in the index.html file, right below the <div> tag for the PDF, add the following code for the JavaScript functions we’ll use in the viewer:

<script>
	WebViewer({
    path: 'WebViewer/lib', // path to the Apryse 'lib' folder on your server
    licenseKey: 'YOUR_LICENSE_KEY', // sign up to get a key at https://dev.apryse.com
    initialDoc: 'WebViewer/samples/files/demo.pdf', // You can also use documents on your server
    disabledElements: [ // hide the default UI elements
	    'printButton',
	    'downloadButton',
	    'view-controls-toggle-button',
	    'leftPanelButton',
	    'searchPanelToggle',
	    'menuButton',
	    'highlightToolButton',
	    'underlineToolButton',
	    'strikeoutToolButton',
	    'squigglyToolButton',
	    'freeTextToolButton',
	    'markInsertTextToolButton',
	    'markReplaceTextToolButton',
	    'stickyToolButton',
	    'calloutToolButton',
	    'freeHandToolButton',
	    'freeHandHighlightToolButton',
	    'eraserToolButton',
	    'page-nav-floating-header',
      'viewControlsButton',
      'viewControlsOverlay',
      'default-top-header',
      'tools-header'
    ]
  }, document.getElementById('viewer'))
  .then(instance => {
});
</script>

It’s important to note that you need to ensure the paths are correct. Notice that we also hid the default WebViewer UI elements. You can choose to add some back in or create your own customized UI.

4. Start the server.

If you have a local server set up already, open a terminal in your project folder and execute:

http-server -a localhost

If you don’t already have a local server, follow these two steps:

a. Open a terminal in your project folder and execute:

npm install -g http-server

b. Start the server by executing:

http-server -a localhost

5. Open the http://localhost:8080/index.html webpage and you should see your PDF.

Congratulations! You’ve just used JavaScript and WebViewer to set up a basic PDF viewer.

Conclusion

Copied to clipboard

Creating a custom JavaScript PDF viewer allows you to tailor its features, appearance, and performance to deliver a customized viewer for any project. While there may be a few extra steps compared to other solutions, this blog lays out the process to make it easy to understand and implement, regardless of your coding experience. Apryse WebViewer offers powerful features in a highly customizable SDK, making it the more favorable solution of the examples we looked at.

Have questions? Contact us to speak with an expert or even reach out on Discord.

Start your free trial of WebViewer today!

Sanity Image

Garry Klooesterman

Senior Technical Content Creator

Share this post

email
linkedIn
twitter