Blog Post

How to Build a JavaScript PDF Viewer with PDF.js

Illustration: How to Build a JavaScript PDF Viewer with PDF.js

In this tutorial, we’ll show you how to build a JavaScript PDF viewer with PDF.js, one of the most popular open source libraries for rendering PDF files in the browser.

A JavaScript PDF viewer uses JavaScript to render and view PDF documents in a web browser without the need to download it to your hard drive or use an external application like a PDF reader.

In the first part of this tutorial, you’ll learn how to render a PDF and how to embed a PDF viewer in the browser with PDF.js. In the second part, you’ll look at how to build a fully featured PDF viewer with the PSPDFKit JavaScript PDF library.

Requirements

To get started, you’ll need:

  • Node.js

  • A package manager like npm or yarn. When you install Node.js, npm is installed by default.

Building a JavaScript PDF Viewer with PDF.js

PDF.js is a JavaScript library built by Mozilla, and it allows you to create a PDF viewer in the browser using JavaScript and the HTML5 <canvas> element.

Since it implements PDF rendering in vanilla JavaScript, it has cross-browser compatibility and doesn’t require additional plugins to be installed. We suggest carefully testing rendering accuracy, as there are many known issues with the render fidelity of PDF.js.

By the end of this tutorial, you’ll be able to:

  • A prebuilt and polished UI for an improved user experience
  • 15+ prebuilt annotation tools to enable document collaboration
  • Support for more file types with client-side PDF, MS Office, and image viewing
  • Dedicated support from engineers to speed up integration

How Does PDF.js Handle PDFs?

With PDF.js, PDFs are downloaded via AJAX and rendered in a <canvas> element using native drawing commands.

PDF.js consists of three different layers:

  • Core — The binary format of a PDF is interpreted in this layer. Using the layer directly is considered advanced use. It handles the heavy PDF.js parsing — an operation that usually takes place in a web worker. This helps keep the main thread responsive at all times.

  • Display — This layer builds upon the core layer and exposes an easy-to-use interface for most day-to-day work. You can use this API to render a page into a <canvas> element with only a couple of lines of JavaScript.

  • Viewer — In addition to providing a programmatic API, PDF.js also comes with a ready-to-use user interface that includes support for search, rotation, a thumbnail sidebar, and many other things.

All you need to do is to download a recent copy of PDF.js and you’re good to go!

Getting Started

To render a specific page of a PDF into a <canvas> element, you can use the display layer.

To get started:

  1. Extract all the files in the downloaded copy of PDF.js.

  2. Move pdf.js and pdf.worker.js from the build/ folder of the download to a new empty folder.

  3. Create an index.html file and an index.js file.

The HTML file needs to point to the pdf.js source code and to the custom application code (index.js).

Creating the HTML File

  1. Create the canvas element, which you want to render the PDF inside of:

<!-- Canvas to place the PDF -->
<canvas id="canvas" class="canvas__container"></canvas>
  1. Create a toolbar inside the <header> element to add navigation and zoom controls to the website.

Next, add some icons for the previous (<), next (>), and zoom buttons. For that, you’ll use the Font Awesome library; all you need to do is include the CDN link for it.

Add the following code to the index.html file:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta
			name="viewport"
			content="width=device-width, initial-scale=1.0"
		/>
		<title>PDF Viewer in JavaScript</title>
		<link
			rel="stylesheet"
			href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"
			integrity="sha512-Fo3rlrZj/k7ujTnHg4CGR2D7kSs0v4LLanw2qksYuRlEzO+tcaEPQogQ0KaoGN26/zrn20ImR1DfuLWnOo7aBA=="
			crossorigin="anonymous"
			referrerpolicy="no-referrer"
		/>
		<link rel="stylesheet" href="style.css" />
	</head>
	<body>
		<header>
			<ul class="navigation">
				<li class="navigation__item">
					<!-- Navigate to the Previous and Next pages -->
					<a href="#" class="previous round" id="prev_page">
						<i class="fas fa-arrow-left"></i>
					</a>

					<!-- Navigate to a specific page -->
					<input type="number" value="1" id="current_page" />

					<a href="#" class="next round" id="next_page">
						<i class="fas fa-arrow-right"></i>
					</a>

					<!-- Page Info -->
					Page
					<span id="page_num"></span>
					of
					<span id="page_count"></span>
				</li>

				<!-- Zoom In and Out -->
				<li class="navigation__item">
					<button class="zoom" id="zoom_in">
						<i class="fas fa-search-plus"></i>
					</button>

					<button class="zoom" id="zoom_out">
						<i class="fas fa-search-minus"></i>
					</button>
				</li>
			</ul>
		</header>

		<!-- Canvas to place the PDF -->
		<canvas id="canvas" class="canvas__container"></canvas>

		<!-- Load PDF.js -->
		<script src="./pdf.js"></script>

		<script src="index.js"></script>
	</body>
</html>

Loading the PDF

  1. Go to the index.js file and create a variable to hold the PDF document. Use the document.querySelector method to access the DOM elements. Also, create an object to hold the initial state of the application:

const pdf = 'document.pdf';

const pageNum = document.querySelector('#page_num');
const pageCount = document.querySelector('#page_count');
const currentPage = document.querySelector('#current_page');
const previousPage = document.querySelector('#prev_page');
const nextPage = document.querySelector('#next_page');
const zoomIn = document.querySelector('#zoom_in');
const zoomOut = document.querySelector('#zoom_out');

const initialState = {
	pdfDoc: null,
	currentPage: 1,
	pageCount: 0,
	zoom: 1,
};

Pass the name of your PDF document to the pdf variable.

  1. Use the pdfjsLib.getDocument(pdf) method to load the PDF file, with the pdf parameter being the path to the PDF file. Then, use the .promise.then() method to handle the promise.

The .then() method takes a callback function that will be called when the promise is fulfilled. Here, you can access the PDF file and set the initialState.pdfDoc to the PDF:

// Load the document.
pdfjsLib
	.getDocument(pdf)
	.promise.then((data) => {
		initialState.pdfDoc = data;
		console.log('pdfDocument', initialState.pdfDoc);

		pageCount.textContent = initialState.pdfDoc.numPages;

		renderPage();
	})
	.catch((err) => {
		alert(err.message);
	});

This API makes heavy use of Promise, a JavaScript feature for handling future values. If you’re not familiar with this pattern, check out the MDN web docs.

After loading the document, set the text content of the pageCount to the number of pages in the document.

  1. As you can see, there’s a function called renderPage(). Use this function to render the current page of your document:

// Render the page.
const renderPage = () => {
	// Load the first page.
	console.log(initialState.pdfDoc, 'pdfDoc');
	initialState.pdfDoc
		.getPage(initialState.currentPage)
		.then((page) => {
			console.log('page', page);

			const canvas = document.querySelector('#canvas');
			const ctx = canvas.getContext('2d');
			const viewport = page.getViewport({
				scale: initialState.zoom,
			});

			canvas.height = viewport.height;
			canvas.width = viewport.width;

			// Render the PDF page into the canvas context.
			const renderCtx = {
				canvasContext: ctx,
				viewport: viewport,
			};

			page.render(renderCtx);

			pageNum.textContent = initialState.currentPage;
		});
};
  • Now that the PDF has been initialized, invoke getPage() on the document instance. The returned Promise resolves with a page object you can use to render the first page of your document.

  • To draw something on the canvas, use the canvas.getContext() method. This method returns a context object you can use to draw on the canvas.

  • The getViewport(scale) method returns a viewport object that represents the page at the given scale.

  • Use the viewport information to set the dimensions of the <canvas> element, and then start the page renderer with the render(options) API.

  • At the end, set the text content of the page number to the current page. This will dynamically update the page number.

pdfjs demo

Until now, you’ve only rendered the first page of your PDF document. Now, you’ll work on making the previous and next icons functional when a user clicks on them:

const showPrevPage = () => {
	if (initialState.pdfDoc === null || initialState.currentPage <= 1)
		return;
	initialState.currentPage--;
	// Render the current page.
	currentPage.value = initialState.currentPage;
	renderPage();
};

const showNextPage = () => {
	if (
		initialState.pdfDoc === null ||
		initialState.currentPage >= initialState.pdfDoc._pdfInfo.numPages
	)
		return;

	initialState.currentPage++;
	currentPage.value = initialState.currentPage;
	renderPage();
};

// Button events.
previousPage.addEventListener('click', showPrevPage);
nextPage.addEventListener('click', showNextPage);

As you can see, the showPrevPage and showNextPage functions are similar. They both check if the current page is the first or last page, and if it is, they do nothing. Otherwise, they decrement or increment the current page and then render the page.

Displaying a Specific Page

Here, you can specify which page to render based on a user input. You’re using the keypress event to listen for the enter key. When the user presses the enter key, you’ll get the page number from the currentPage input and check if it’s between the ranges of PDF pages. If it is, you’ll set the currentPage to the desired page and render the page:

// Keypress event.
currentPage.addEventListener('keypress', (event) => {
	if (initialState.pdfDoc === null) return;
	// Get the key code.
	const keycode = event.keyCode ? event.keyCode : event.which;

	if (keycode === 13) {
		// Get the new page number and render it.
		let desiredPage = currentPage.valueAsNumber;
		initialState.currentPage = Math.min(
			Math.max(desiredPage, 1),
			initialState.pdfDoc._pdfInfo.numPages,
		);

		currentPage.value = initialState.currentPage;
		renderPage();
	}
});

For cases when a user types a number that’s either negative or greater than the number of pages, set the currentPage to the first or last page, respectively, and display the page.

Adding a Zoom Feature to PDF.js

You’re done navigating through the PDF pages. Now, you’ll add the zoom feature:

// Zoom events.
zoomIn.addEventListener('click', () => {
	if (initialState.pdfDoc === null) return;
	initialState.zoom *= 4 / 3;
	renderPage();
});

zoomOut.addEventListener('click', () => {
	if (initialState.pdfDoc === null) return;
	initialState.zoom *= 2 / 3;
	renderPage();
});

Similar to how it is with navigation, when the user clicks on the zoomIn or zoomOut buttons, you’ll increment or decrement the zoom value and then render the page.

Serving Your Website

  1. Make sure to copy a PDF file into the folder and rename it to document.pdf.

  2. Install the serve package:

npm install --global serve
  1. Serve the contents of the current directory:

serve -l 8080 .
  1. Navigate to http://localhost:8080 to view the website.

Embedding the PDF.js Viewer in an HTML Window

While the display layer provides fine-grained control over which parts of a PDF document are rendered, there are times when you might prefer a ready-to-use viewer. Luckily, PDF.js has you covered. In this part, you’ll integrate the PDF.js default viewer into the website.

  1. Go back to the pdfjs-dist folder you downloaded earlier.

  2. Copy the entire web/ folder into a new directory.

  3. Create a build/ folder inside the new directory and copy the pdf.js and pdf.worker.js files there.

  4. Create an index.html file that will include the viewer via an <iframe>. Locate this file in the root of your project.

This allows you to easily embed the viewer into an existing webpage. The viewer is configured via URL parameters, a list of which can be found here. For this example, you’ll only configure the source PDF file, and you no longer need the index.js file:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>PDF.js Example</title>
	</head>
	<body>
		<iframe
			src="/web/viewer.html?file=document.pdf"
			width="1000px"
			height="1000px"
			style="border: none"
		/>
	</body>
</html>

For more advanced features (like saving the PDF document to your web server again), you can start modifying the viewer.html file provided by PDF.js. You can find the viewer.html file inside the web/ folder.

  1. Now, place your PDF file inside the web directory.

  2. Run serve -l 8080 . from the root directory to serve the contents of the index.html file, and navigate to the http://localhost:8080/web/viewer?file=document.pdf URL to view the PDF. According to the name of your local PDF file, you can change the document.pdf to your PDF file name.

  3. Your file directory now should look like this:

pdfjs-viewer-example
├── build 
|    └── pdf.js
|    └── pdf.worker.js
├── web
|    └── viewer.html
|    └── document.pdf
|    └── ...other files
└── index.html

pdfjs demo

Information

You can access the demo applications on GitHub.

Final Words on PDF.js

All in all, PDF.js is a great solution for many use cases. However, sometimes your business requires more complex features, such as the following, for handling PDFs in the browser:

  • PDF annotation support — PDF.js will only render annotations that were already in the source file, and you can use the core API to access raw annotation data. It doesn’t have annotation editing support, so your users won’t be able to create, update, or delete annotations to review a PDF document.
  • PDF form filling — While PDF.js has started working with interactive forms, our testing found there are still a lot of issues left open. For example, form buttons and actions aren’t supported, making it impossible to submit forms to your web service.
  • Mobile support — PDF.js comes with a clean mobile interface, but it misses features that provide a great user experience and are expected nowadays, like pinch-to-zoom. Additionally, downloading an entire PDF document for mobile devices might result in a big performance penalty.
  • Persistent management — With PDF.js, there’s no option to easily share, edit, and annotate PDF documents across a broad variety of devices (whether it be other web browsers, native apps, or more). If your business relies on this service, consider looking into a dedicated annotation syncing framework like PSPDFKit Instant.
  • Digital signatures — PDF.js currently has no support for digital signatures, which are used to verify the authenticity of a filled-out PDF.
  • Advanced view options — The PDF.js viewer only supports a continuous page view mode, wherein all pages are laid out in a list and the user can scroll through them vertically. Single- or double-page modes — where only one (or two) pages are shown at once (a common option to make it easier to read books or magazines) — aren’t possible.
  • Render fidelity — There are many known problems with the render fidelity of PDF.js, where PDFs look different, have different colors or shapes, or even miss parts of the document altogether.

If your business relies on any of the above features, consider looking into alternatives.

Building a JavaScript PDF Viewer with PSPDFKit

We at PSPDFKit work on the next generation of PDF viewers for the web. We offer a commercial JavaScript PDF viewer library that can easily be integrated into your web application. It comes with 30+ features that let you view, annotate, edit, and sign documents directly in your browser. Out of the box, it has a polished and flexible UI that you can extend or simplify based on your unique use case.

  • A prebuilt and polished UI
  • 15+ annotation tools
  • Support for multiple file types
  • Dedicated support from engineers

Adding PSPDFKit to Your Project

  1. Install the pspdfkit package from npm. If you prefer, you can also download PSPDFKit for Web manually:

npm install pspdfkit
  1. For PSPDFKit for Web to work, it’s necessary to copy the directory containing all the required library files (artifacts) to the assets folder. Use the following command to do this:

cp -R ./node_modules/pspdfkit/dist/ ./assets/

Make sure your assets directory contains the pspdfkit.js file and a pspdfkit-lib directory with the library assets.

Integrating into Your Project

  1. Add the PDF document you want to display to your project’s directory. You can use our demo document as an example.

  2. Add an empty <div> element with a defined height to where PSPDFKit will be mounted:

<div id="pspdfkit" style="height: 100vh;"></div>
  1. Include pspdfkit.js in your HTML page:

<script src="assets/pspdfkit.js"></script>
  1. Initialize PSPDFKit for Web in JavaScript by calling PSPDFKit.load():

<script>
	PSPDFKit.load({
		container: "#pspdfkit",
  		document: "document.pdf" // Add the path to your document here.
	})
	.then(function(instance) {
		console.log("PSPDFKit loaded", instance);
	})
	.catch(function(error) {
		console.error(error.message);
	});
</script>

You can see the full index.html file below:

<!DOCTYPE html>
<html>
	<head>
		<title>My App</title>
		<!-- Provide proper viewport information so that the layout works on mobile devices. -->
		<meta
			name="viewport"
			content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
		/>
	</head>
	<body>
		<!-- Element where PSPDFKit will be mounted. -->
		<div id="pspdfkit" style="height: 100vh;"></div>

		<script src="assets/pspdfkit.js"></script>

		<script>
			PSPDFKit.load({
				container: '#pspdfkit',
				document: 'document.pdf',
			})
				.then(function (instance) {
					console.log('PSPDFKit loaded', instance);
				})
				.catch(function (error) {
					console.error(error.message);
				});
		</script>
	</body>
</html>

You can serve the application with the serve package like you did for PDF.js.

You should now have our JavaScript PDF viewer up and running in your web application. If you hit any snags, don’t hesitate to reach out to our support team for help.

pspdfkit demo

If you want to download PSPDFKit manually or integrate it as a module, you can check out our JavaScript getting started guide.

Information

Interact with the sandbox by clicking the left rectangle icon and selecting Editor > Show Default Layout. To edit, sign in with GitHub — click the rectangle icon again and choose Sign in. To preview the result, click the rectangle icon once more and choose Editor > Embed Preview. For the full example, click the Open Editor button. Enjoy experimenting with the project!

Adding Even More Capabilities

Once you’ve deployed your viewer, you can start customizing it to meet your specific requirements or easily add more capabilities. To help you get started, here are some of our most popular JavaScript guides:

Conclusion

In this tutorial, you first looked at how to build a JavaScript PDF viewer with the PDF.js library. In the second part, you learned how to build the viewer with the PSPDFKit JavaScript PDF viewer.

We created similar how-to blog posts using different web frameworks and libraries:

To see a list of all web frameworks, start your free trial. Or, launch our demo to see our viewer in action.

Related Products
Share Post
Free 60-Day Trial Try PSPDFKit in your app today.
Free Trial

Related Articles

Explore more
PRODUCTS  |  Web • Releases • Components

PSPDFKit for Web 2024.3 Features New Stamps and Signing UI, Export to Office Formats, and More

PRODUCTS  |  Web • Releases • Components

PSPDFKit for Web 2024.2 Features New Unified UI Icons, Shadow DOM, and Tab Ordering

PRODUCTS  |  Web

Now Available for Public Preview: New Document Authoring Experience Provides Glimpse into the Future of Editing