How to Create a JavaScript PDF Viewer

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.

DemoDemoDemo

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:

Demo With BackgroundDemo With BackgroundDemo With Background

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.

Page ControlsPage ControlsPage Controls

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.

Zoom DemoZoom DemoZoom Demo

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.