The Portable Document Format, or PDF for short, is ideal for sharing documents containing lots of precisely formatted text and images, especially if they’re likely to be printed or read offline. Although most modern browsers can display PDF files, they do so using a PDF viewer that runs in an independent tab or window, forcing users to leave your website.
PDF.js is an open-source JavaScript library that allows you to parse and render PDF files right inside your web pages. In this tutorial, I’ll show you how to use it to create a fully fledged custom JavaScript PDF viewer from scratch.
1. Creating a User Interface
We’ll start this JS PDF viewer tutorial by creating a new web page and adding the usual HTML5 boilerplate code to it.
1 |
<!doctype html>
|
2 |
|
3 |
<html lang="en"> |
4 |
<head>
|
5 |
<meta charset="utf-8"> |
6 |
<meta name="viewport" content="width=device-width, initial-scale=1"> |
7 |
<title>My PDF Viewer</title> |
8 |
</head>
|
9 |
<body>
|
10 |
|
11 |
</body>
|
12 |
</html>
|
Next, inside the <body>
, create a <div>
element that can serve as a container for our PDF viewer in JS.
1 |
<div id="my_pdf_viewer"> |
2 |
|
3 |
</div>
|
At the heart of our JavaScript PDF viewer will be an HTML5 <canvas>
element. We’ll be rendering the pages of our PDF files inside it. So add the following code inside the <div>
element.
1 |
<div id="canvas_container"> |
2 |
<canvas id="pdf_renderer"></canvas> |
3 |
</div>
|
To keep things simple, we’ll be rendering only one page inside the canvas at any given time. We will, however, allow users to switch to the previous page or the next page by pressing a button. Additionally, to display the current page number, and to allow users to jump to any page they desire, our interface will have an input field.
1 |
<div id="navigation_controls"> |
2 |
<button id="go_previous">Previous</button> |
3 |
<input id="current_page" value="1" type="number"/> |
4 |
<button id="go_next">Next</button> |
5 |
</div>
|
To support zoom operations, add two more buttons to the interface: one to zoom in and one to zoom out.
1 |
<div id="zoom_controls"> |
2 |
<button id="zoom_in">+</button> |
3 |
<button id="zoom_out">-</button> |
4 |
</div>
|
At the end of this section, the web page code looks like this.
1 |
<!doctype html>
|
2 |
<html lang="en"> |
3 |
<head>
|
4 |
<meta charset="utf-8"> |
5 |
<meta name="viewport" content="width=device-width, initial-scale=1"> |
6 |
<title>My PDF Viewer</title> |
7 |
</head>
|
8 |
<body>
|
9 |
<div id="my_pdf_viewer"> |
10 |
<div id="canvas_container"> |
11 |
<canvas id="pdf_renderer"></canvas> |
12 |
</div>
|
13 |
|
14 |
<div id="navigation_controls"> |
15 |
<button id="go_previous">Previous</button> |
16 |
<input id="current_page" value="1" type="number"/> |
17 |
<button id="go_next">Next</button> |
18 |
</div>
|
19 |
|
20 |
<div id="zoom_controls"> |
21 |
<button id="zoom_in">+</button> |
22 |
<button id="zoom_out">-</button> |
23 |
</div>
|
24 |
</div>
|
25 |
</body>
|
26 |
</html>
|
2. Getting PDF.js
Now that a bare-bones user interface for our JavaScript PDF viewer is ready, let’s add PDF.js to our web page. Because the latest version of the library is available on CDNJS, we can do so by simply adding the following lines towards the end of the web page.
1 |
<script
|
2 |
src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.0.943/pdf.min.js"> |
3 |
</script>
|
If you prefer to use a local copy of the library, you can download it from the pdfjs-dist repository.
3. Loading a PDF File
Before we start loading a PDF file, let’s create a simple JavaScript object to store the state of our PDF viewer in JS. Inside it, we’ll have three items: a reference to the PDF file itself, the current page index, and the current zoom level.
1 |
<script>
|
2 |
var myState = { |
3 |
pdf: null, |
4 |
currentPage: 1, |
5 |
zoom: 1 |
6 |
}
|
7 |
|
8 |
// more code here
|
9 |
</script>
|
At this point, we can load our PDF file by calling the getDocument()
method of the pdfjsLib
object, which runs asynchronously.
1 |
pdfjsLib.getDocument('./my_document.pdf').then((pdf) => { |
2 |
|
3 |
// more code here |
4 |
|
5 |
}); |
Note that the getDocument()
method internally uses an XMLHttpRequest
object to load the PDF file. This means that the file must be present either on your own web server or on a server that allows cross-origin requests.
If you don’t have a PDF file handy, you can get the one I’m using from Wikipedia.
Once the PDF file has been loaded successfully, we can update the pdf
property of our state object.
Lastly, add a call to a function named render()
so that our PDF viewer in JS automatically renders the first page of the PDF file. We’ll define the function in the next step.
4. Rendering a Page
By calling the getPage()
method of the pdf
object and passing a page number to it, we can get a reference to any page in the PDF file. For now, let’s pass the currentPage
property of our state object to it. This method too returns a promise, so we’ll need a callback function to handle its result.
Accordingly, create a new function called render()
containing the following code:
1 |
function render() { |
2 |
myState.pdf.getPage(myState.currentPage).then((page) => { |
3 |
|
4 |
// more code here |
5 |
|
6 |
}); |
7 |
} |
To actually render a page, we must call the render()
method of the page
object available inside the callback. As arguments, the method expects the 2D context of our canvas and a PageViewport
object, which we can get by calling the getViewport()
method. Because the getViewport()
method expects the desired zoom level as an argument, we must pass the zoom
property of our state object to it.
1 |
var canvas = document.getElementById("pdf_renderer"); |
2 |
var ctx = canvas.getContext('2d'); |
3 |
|
4 |
var viewport = page.getViewport(myState.zoom); |
The dimensions of the viewport depend on the original size of the page and the zoom level. In order to make sure that the entire viewport is rendered on our canvas, we must now change the size of our canvas to match that of the viewport. Here’s how:
1 |
canvas.width = viewport.width; |
2 |
canvas.height = viewport.height; |
At this point, we can go ahead and render the page.
1 |
page.render({ |
2 |
canvasContext: ctx, |
3 |
viewport: viewport |
4 |
}); |
Putting it all together, the whole source code looks like this.
1 |
<!doctype html>
|
2 |
|
3 |
<html lang="en"> |
4 |
<head>
|
5 |
<meta charset="utf-8"> |
6 |
<meta name="viewport" content="width=device-width, initial-scale=1"> |
7 |
<title>My PDF Viewer</title> |
8 |
<script
|
9 |
src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.0.943/pdf.min.js"> |
10 |
</script>
|
11 |
</head>
|
12 |
<body>
|
13 |
<div id="my_pdf_viewer"> |
14 |
<div id="canvas_container"> |
15 |
<canvas id="pdf_renderer"></canvas> |
16 |
</div>
|
17 |
|
18 |
<div id="navigation_controls"> |
19 |
<button id="go_previous">Previous</button> |
20 |
<input id="current_page" value="1" type="number"/> |
21 |
<button id="go_next">Next</button> |
22 |
</div>
|
23 |
|
24 |
<div id="zoom_controls"> |
25 |
<button id="zoom_in">+</button> |
26 |
<button id="zoom_out">-</button> |
27 |
</div>
|
28 |
</div>
|
29 |
|
30 |
<script>
|
31 |
var myState = { |
32 |
pdf: null, |
33 |
currentPage: 1, |
34 |
zoom: 1 |
35 |
}
|
36 |
|
37 |
pdfjsLib.getDocument('./my_document.pdf').then((pdf) => { |
38 |
|
39 |
myState.pdf = pdf; |
40 |
render(); |
41 |
|
42 |
});
|
43 |
|
44 |
function render() { |
45 |
myState.pdf.getPage(myState.currentPage).then((page) => { |
46 |
|
47 |
var canvas = document.getElementById("pdf_renderer"); |
48 |
var ctx = canvas.getContext('2d'); |
49 |
|
50 |
var viewport = page.getViewport(myState.zoom); |
51 |
|
52 |
canvas.width = viewport.width; |
53 |
canvas.height = viewport.height; |
54 |
|
55 |
page.render({ |
56 |
canvasContext: ctx, |
57 |
viewport: viewport |
58 |
});
|
59 |
});
|
60 |
}
|
61 |
</script>
|
62 |
</body>
|
63 |
</html>
|
If you try opening the web page in a browser, you should now be able to see the first page of your PDF file.
You may have noticed that the size of our PDF viewer currently depends on the size of the page being rendered and the zoom level. This is not ideal because we don’t want the layout of our web page to be affected while users interact with the PDF viewer in JavaScript.
To fix this, all we need to do is give a fixed width and height to the <div>
element encapsulating our canvas and set its overflow
CSS property to auto
. This property, when necessary, adds scroll bars to the <div>
element, allowing users to scroll both horizontally and vertically.
Add the following code inside the <head>
tag of the web page:
1 |
<style>
|
2 |
#canvas_container { |
3 |
width: 800px; |
4 |
height: 450px; |
5 |
overflow: auto; |
6 |
}
|
7 |
</style>
|
You are, of course, free to change the width and height or even use media queries to make the <div>
element match your requirements.
Optionally, you can include the following CSS rules to make the <div>
element seem more distinct:
1 |
#canvas_container { |
2 |
background: #333; |
3 |
text-align: center; |
4 |
border: solid 3px; |
5 |
} |
If you refresh the web page now, you should see something like this on your screen:
5. Changing the Current Page
Our JavaScript PDF viewer is currently capable of showing only the first page of any PDF file given to it. To allow users to change the page being rendered, we must now add click event listeners to the go_previous
and go_next
buttons we created earlier.
Inside the event listener of the go_previous
button, we must decrement the currentPage
property of our state object, making sure that it doesn’t fall below 1
. After we do so, we can simply call the render()
function again to render the new page.
Additionally, we must update the value of the current_page
text field so that it displays the new page number. The following code shows you how:
1 |
document.getElementById('go_previous') |
2 |
.addEventListener('click', (e) => { |
3 |
if(myState.pdf == null|| myState.currentPage == 1) |
4 |
return; |
5 |
|
6 |
myState.currentPage -= 1; |
7 |
document.getElementById("current_page").value = myState.currentPage; |
8 |
render(); |
9 |
}); |
Similarly, inside the event listener of the go_next
button, we must increment the currentPage
property, while ensuring that it doesn’t exceed the number of pages present in the PDF file, which we can determine using the numPages
property of the _pdfInfo
object.
1 |
document.getElementById('go_next') |
2 |
.addEventListener('click', (e) => { |
3 |
if(myState.pdf == null || myState.currentPage > myState.pdf._pdfInfo.numPages) |
4 |
return; |
5 |
|
6 |
myState.currentPage += 1; |
7 |
document.getElementById("current_page").value = myState.currentPage; |
8 |
render(); |
9 |
}); |
Lastly, we must add a key press event listener to the current_page
text field so that users can directly jump to any page they desire by simply typing in a page number and hitting the Enter key. Inside the event listener, we need to make sure that the number the user has entered is both greater than zero and less than or equal to the numPages
property.
1 |
document.getElementById('current_page') |
2 |
.addEventListener('keypress', (e) => { |
3 |
if(myState.pdf == null) return; |
4 |
|
5 |
// Get key code |
6 |
var code = (e.keyCode ? e.keyCode : e.which); |
7 |
|
8 |
// If key code matches that of the Enter key |
9 |
if(code == 13) { |
10 |
var desiredPage = document.getElementById('current_page').valueAsNumber; |
11 |
|
12 |
if(desiredPage >= 1 && desiredPage <= myState.pdf._pdfInfo.numPages) { |
13 |
myState.currentPage = desiredPage; |
14 |
document.getElementById("current_page").value = desiredPage; |
15 |
render(); |
16 |
}
|
17 |
}
|
18 |
});
|
Our PDF viewer in JavaScript can now display any page of the PDF file.
6. Changing the Zoom Level
Because our render()
function already uses the zoom
property of the state object while rendering a page, adjusting the zoom level is as easy as incrementing or decrementing the property and calling the function.
Inside the on-click event listener of the zoom_in
button, let’s increment the zoom
property by 0.5
.
1 |
document.getElementById('zoom_in') |
2 |
.addEventListener('click', (e) => { |
3 |
if(myState.pdf == null) return; |
4 |
myState.zoom += 0.5; |
5 |
|
6 |
render(); |
7 |
}); |
And inside the on-click event listener of the zoom_out
button, let’s decrement the zoom
property by 0.5
.
1 |
document.getElementById('zoom_out') |
2 |
.addEventListener('click', (e) => { |
3 |
if(myState.pdf == null) return; |
4 |
myState.zoom -= 0.5; |
5 |
|
6 |
render(); |
7 |
}); |
You are free to add upper and lower bounds to the zoom
property, but they are not required.
This is what it looks like when it’s all put together.
1 |
<!doctype html>
|
2 |
|
3 |
<html lang="en"> |
4 |
<head>
|
5 |
<meta charset="utf-8"> |
6 |
<meta name="viewport" content="width=device-width, initial-scale=1"> |
7 |
<title>My PDF Viewer</title> |
8 |
<script
|
9 |
src="http://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.0.943/pdf.min.js"> |
10 |
</script>
|
11 |
|
12 |
<style>
|
13 |
#canvas_container { |
14 |
width: 800px; |
15 |
height: 450px; |
16 |
overflow: auto; |
17 |
}
|
18 |
|
19 |
#canvas_container { |
20 |
background: #333; |
21 |
text-align: center; |
22 |
border: solid 3px; |
23 |
}
|
24 |
</style>
|
25 |
</head>
|
26 |
<body>
|
27 |
<div id="my_pdf_viewer"> |
28 |
<div id="canvas_container"> |
29 |
<canvas id="pdf_renderer"></canvas> |
30 |
</div>
|
31 |
|
32 |
<div id="navigation_controls"> |
33 |
<button id="go_previous">Previous</button> |
34 |
<input id="current_page" value="1" type="number"/> |
35 |
<button id="go_next">Next</button> |
36 |
</div>
|
37 |
|
38 |
<div id="zoom_controls"> |
39 |
<button id="zoom_in">+</button> |
40 |
<button id="zoom_out">-</button> |
41 |
</div>
|
42 |
</div>
|
43 |
|
44 |
<script>
|
45 |
var myState = { |
46 |
pdf: null, |
47 |
currentPage: 1, |
48 |
zoom: 1 |
49 |
}
|
50 |
|
51 |
pdfjsLib.getDocument('./my_document.pdf').then((pdf) => { |
52 |
|
53 |
myState.pdf = pdf; |
54 |
render(); |
55 |
|
56 |
});
|
57 |
|
58 |
function render() { |
59 |
myState.pdf.getPage(myState.currentPage).then((page) => { |
60 |
|
61 |
var canvas = document.getElementById("pdf_renderer"); |
62 |
var ctx = canvas.getContext('2d'); |
63 |
|
64 |
var viewport = page.getViewport(myState.zoom); |
65 |
|
66 |
canvas.width = viewport.width; |
67 |
canvas.height = viewport.height; |
68 |
|
69 |
page.render({ |
70 |
canvasContext: ctx, |
71 |
viewport: viewport |
72 |
});
|
73 |
});
|
74 |
}
|
75 |
|
76 |
document.getElementById('go_previous').addEventListener('click', (e) => { |
77 |
if(myState.pdf == null || myState.currentPage == 1) |
78 |
return; |
79 |
myState.currentPage -= 1; |
80 |
document.getElementById("current_page").value = myState.currentPage; |
81 |
render(); |
82 |
});
|
83 |
|
84 |
document.getElementById('go_next').addEventListener('click', (e) => { |
85 |
if(myState.pdf == null || myState.currentPage > myState.pdf._pdfInfo.numPages) |
86 |
return; |
87 |
myState.currentPage += 1; |
88 |
document.getElementById("current_page").value = myState.currentPage; |
89 |
render(); |
90 |
});
|
91 |
|
92 |
document.getElementById('current_page').addEventListener('keypress', (e) => { |
93 |
if(myState.pdf == null) return; |
94 |
|
95 |
// Get key code
|
96 |
var code = (e.keyCode ? e.keyCode : e.which); |
97 |
|
98 |
// If key code matches that of the Enter key
|
99 |
if(code == 13) { |
100 |
var desiredPage = |
101 |
document.getElementById('current_page').valueAsNumber; |
102 |
|
103 |
if(desiredPage >= 1 && desiredPage <= myState.pdf._pdfInfo.numPages) { |
104 |
myState.currentPage = desiredPage; |
105 |
document.getElementById("current_page").value = desiredPage; |
106 |
render(); |
107 |
}
|
108 |
}
|
109 |
});
|
110 |
|
111 |
document.getElementById('zoom_in').addEventListener('click', (e) => { |
112 |
if(myState.pdf == null) return; |
113 |
myState.zoom += 0.5; |
114 |
render(); |
115 |
});
|
116 |
|
117 |
document.getElementById('zoom_out').addEventListener('click', (e) => { |
118 |
if(myState.pdf == null) return; |
119 |
myState.zoom -= 0.5; |
120 |
render(); |
121 |
});
|
122 |
</script>
|
123 |
</body>
|
124 |
</html>
|
Our PDF viewer in JavaScript is ready. If you refresh the web page again, you should be able to view all the pages present in the PDF document in JavaScript and also zoom in to or zoom out of them.
Congratulations! You’ve Learned to Create a Viewer for PDF Documents in JavaScript!
You now know how to use PDF.js to create a custom JavaScript PDF viewer for your website. With your new viewer for PDF documents in JavaScript, you can confidently offer a seamless user experience while displaying your organization’s brochures, white papers, forms, and other documents generally meant to be distributed as hard copies.
You can learn more about PDF.js on the official GitHub repo.