Easy Web PDF Viewer With jQuery And pdf.js - pdfjs-viewer

File Size: 199 KB
Views Total: 25914
Last Update:
Publish Date:
Official Website: Go to website
License: MIT
   
Easy Web PDF Viewer With jQuery And pdf.js - pdfjs-viewer

As web developers, we are always on the lookout for new ways to improve our user's experience. That's why I'm excited to share with you a new jQuery plugin called pdfjs-viewer.

This plugin is based on mozilla's pdf.js library, and provides a simple way for your users to view PDF documents directly in their web browser.

In this tutorial, I'll show you how to install and use the pdfjs-viewer jQuery plugin, as well as some of its features. So let's get started!

How to use it:

1. Load the necessary jQuery and pdf.js libraries in the document.

<script src="/path/to/cdn/jquery.min.js"></script>
<script src="/path/to/cdn/pdf.min.js"></script>

2. Load the OPTIONAL Material Icons.

<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet">

3. Load the pdfjs-viewer plugin's files.

<!-- Core -->
<script src="/js/pdfjs-viewer.js"></script>
<link rel="stylesheet" href="/css/pdfjs-viewer.css">
<!-- Optional Toolbar Stylesheet -->
<link rel="stylesheet" href="/css/pdftoolbar.css">

4. Initialize the pdf.js library.

// Let's initialize the PDFjs library
var pdfjsLib = window['pdfjs-dist/build/pdf'];

// The workerSrc property shall be specified.
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js';

5. Emebd a PDF viewer in an HTML element you specify.

<div class="pdfjs-viewer">
  ...
</div>
var pdfViewer = new PDFjsViewer($('.pdfjs-viewer'));
// load a PDF file
pdfViewer.loadDocument('test.pdf');
<!-- Or Using pdf-document attribute -->
<div id="viewer" class="pdfjs-viewer" pdf-document="test.pdf" initial-zoom="fit">
  ...
</div>
let pdfViewer = document.getElementById("viewer");

5. Add a custom toolbar to the PDF viewer.

<div class="row col-6 offset-md-3 pdfviewer p-0 row h-100">
  <div class="pdfjs-toolbar text-center row m-0 p-0">
    <div class="col-12 col-lg-6 my-1">
      <button class="btn btn-secondary btn-sm btn-first" onclick="pdfViewer.first()"><i class="material-icons-outlined">skip_previous</i></button>
      <button class="btn btn-secondary btn-sm btn-prev" onclick="pdfViewer.prev(); return false;"><i class="material-icons-outlined">navigate_before</i></button>
      <span class="pageno"></span>
      <button class="btn btn-secondary btn-sm btn-next" onclick="pdfViewer.next(); return false;"><i class="material-icons-outlined">navigate_next</i></button>
      <button class="btn btn-secondary btn-sm btn-last" onclick="pdfViewer.last()"><i class="material-icons-outlined">skip_next</i></button>
    </div>
    <div class="col-12 col-lg-6 my-1">
      <button class="btn btn-secondary btn-sm" onclick="pdfViewer.setZoom('out')"><i class="material-icons-outlined">zoom_out</i></button>
      <span class="zoomval">100%</span>
      <button class="btn btn-secondary btn-sm" onclick="pdfViewer.setZoom('in')"><i class="material-icons-outlined">zoom_in</i></button>
      <button class="btn btn-secondary btn-sm ms-3" onclick="pdfViewer.setZoom('width')"><i class="material-icons-outlined">swap_horiz</i></button>
      <button class="btn btn-secondary btn-sm" onclick="pdfViewer.setZoom('height')"><i class="material-icons-outlined">swap_vert</i></button>
      <button class="btn btn-secondary btn-sm" onclick="pdfViewer.setZoom('fit')"><i class="material-icons-outlined">fit_screen</i></button>
    </div>
  </div>
  <div class="pdfjs-viewer h-100" pdf-document="test.pdf" initial-zoom="fit" on-document-ready="pdfViewer = this.pdfViewer;">
  </div>    
</div>
var pdfViewer;

6. This example shows how to create a fullscreen PDF viewer with thumbnail support.

<div class="pdfviewer">
  <div class="pdfjs-toolbar">
    <button class="pushed" onclick="togglethumbs(this);"><i class="material-icons-outlined">view_sidebar</i></button>
    <div class="v-sep"></div>
    <button onclick="pdfViewer.prev();"><i class="material-icons-outlined">arrow_upward</i></button>
    <div class="v-sep"></div>
    <button onclick="pdfViewer.next();"><i class="material-icons-outlined">arrow_downward</i></button>
    <input id="pageno" class="pageno" type="number" class="form-control form-control-sm d-inline w-auto" value="1" min="1" max="1000" onchange="pdfViewer.scrollToPage(parseInt(this.value))">
    <span id="pagecount" class="pageno"></span>
    <div class="divider"></div>
    <button onclick="pdfViewer.setZoom('in')"><i class="material-icons-outlined">add</i></button>
    <div class="v-sep"></div>
    <button onclick="pdfViewer.setZoom('out')"><i class="material-icons-outlined">remove</i></button>
    <div class="dropdown">
      <div class="dropdown-value" onclick="this.parentNode.classList.toggle('show');">
        <span class="zoomval">100%</span>
        <i class="material-icons-outlined">
        keyboard_arrow_down
        </i>                    
      </div>
      <div class="dropdown-content" onclick="this.parentNode.classList.toggle('show');">
        <a href="#" onclick='pdfViewer.setZoom("width"); return false;'>Adjust width</a>
        <a href="#" onclick='pdfViewer.setZoom("height"); return false;'>Adjust height</a>
        <a href="#" onclick='pdfViewer.setZoom("fit"); return false;'>Fit page</a>
        <a href="#" onclick='pdfViewer.setZoom(0.5); return false;'>50%</a>
        <a href="#" onclick='pdfViewer.setZoom(0.75); return false;'>75%</a>
        <a href="#" onclick='pdfViewer.setZoom(1); return false;'>100%</a>
        <a href="#" onclick='pdfViewer.setZoom(1.25); return false;'>125%</a>
        <a href="#" onclick='pdfViewer.setZoom(1.5); return false;'>150%</a>
        <a href="#" onclick='pdfViewer.setZoom(2); return false;'>200%</a>
        <a href="#" onclick='pdfViewer.setZoom(3); return false;'>300%</a>
        <a href="#" onclick='pdfViewer.setZoom(4); return false;'>400%</a>
      </div>
    </div>
    <button id="hideselected" onclick="hideselected();"><i class="material-icons-outlined">block</i></button>
    <div class="divider"></div>
    <label class="button" for="opendoc"><i class="material-icons-outlined">file_open</i></label>
    <input id="opendoc" type="file" accept="application/pdf">
    <a id="filedownload" class="button"><i class="material-icons-outlined">file_download</i></a>
    <div class="dropdown dropdown-right">
      <div onclick="this.parentNode.classList.toggle('show');">
        <button><i class="material-icons-outlined">keyboard_double_arrow_right</i></button>
      </div>
      <div class="dropdown-content" onclick="this.parentNode.classList.toggle('show');">
        <a href="#" onclick='pdfViewer.scrollToPage(1); return false;'><i class="material-icons-outlined">vertical_align_top</i>First page</a>
        <a href="#" onclick='pdfViewer.scrollToPage(pdfViewer.pdf.numPages); return false;'><i class="material-icons-outlined">vertical_align_bottom</i>Last page</a>
        <div class="h-sep"></div>
        <a href="#" onclick='pdfViewer.rotate(-90, true); pdfThumbnails.rotate(-90, true).then(() => pdfThumbnails.setZoom("fit"));'><i class="material-icons-outlined">rotate_90_degrees_ccw</i>Rotate countrary clockwise</a>
        <a href="#" onclick='pdfViewer.rotate(90, true); pdfThumbnails.rotate(90, true).then(() => pdfThumbnails.setZoom("fit"));'><i class="material-icons-outlined">rotate_90_degrees_cw</i>Rotate clockwise</a>
        <div class="h-sep"></div>
        <a href="#" onclick='document.querySelector(".pdfjs-viewer").classList.remove("horizontal-scroll"); pdfViewer.refreshAll();'><i class="material-icons-outlined">more_vert</i>Vertical scroll</a>
        <a href="#" onclick='setHorizontal()'><i class="material-icons-outlined">more_horiz</i>Horizontal scroll</a>
      </div>
    </div>
  </div>
  <div class="pdfviewer-container">
    <div class="thumbnails pdfjs-viewer">
    </div>
    <div class="maindoc pdfjs-viewer">
      <div class="pdfpage placeholder">
        <p class="my-auto mx-auto">Cargue un fichero</p>
      </div>
    </div>
  </div>
</div>
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const PDFFILE = urlParams.get('doc')??"test.pdf";

function dataURItoBinArray(data) {
    // taken from: https://stackoverflow.com/a/11954337/14699733
    var binary = atob(data);
    var array = [];
    for(var i = 0; i < binary.length; i++) {
        array.push(binary.charCodeAt(i));
    }
    return new Uint8Array(array);
}
/** Function to load a PDF file using the input=file API */
document.querySelector("#opendoc").addEventListener("change", function(e) {
    let file = e.target;
    let reader = new FileReader();
    reader.onload = async function() {
        await pdfViewer.loadDocument({data: dataURItoBinArray(reader.result.replace(/^data:.*;base64,/,""))});
        await pdfThumbnails.loadDocument({data: dataURItoBinArray(reader.result.replace(/^data:.*;base64,/,""))}).then(() => pdfThumbnails.setZoom("fit"));
    }
    if (file.files.length > 0) {
        reader.readAsDataURL(file.files[0]);
        document.querySelector('#filedownload').download = document.querySelector('#opendoc').files[0].name;
    }
});
/** Sets the document in horizontal scroll by changing the class for the pages container and refreshing the document 
 *    so that the pages may be displayed in horizontal scroll if they were not visible before */
function setHorizontal() {
    document.querySelector(".maindoc").classList.add("horizontal-scroll"); 
    pdfViewer.refreshAll();    
}
/** Toggles the visibility of the thumbnails */
function togglethumbs(el) {
    if (el.classList.contains('pushed')) {
        el.classList.remove('pushed');
        document.querySelector('.thumbnails').classList.add('hide');
    } else {
        el.classList.add('pushed');
        document.querySelector('.thumbnails').classList.remove('hide');
    }
}
/** Toggles hiding the current page */
function hideselected() {
    let $selected = pdfThumbnails.$container.find('.selected');
    let i = $selected.data('page');
    $selected.toggleClass('hidden');
    pdfViewer.$container.find('.pdfpage[data-page="' + i + '"]').toggleClass('hidden');
    pdfViewer.scrollToPage(i);
}
/** Now create the PDFjsViewer object in the DIV */
let pdfViewer = new PDFjsViewer($('.maindoc'), {
    zoomValues: [ 0.5, 0.75, 1, 1.25, 1.5, 2, 3, 4 ],

    /** Update the zoom value in the toolbar */
    onZoomChange: function(zoom) {
        zoom = parseInt(zoom * 10000) / 100;
        $('.zoomval').text(zoom + '%');
    },

    /** Update the active page */
    onActivePageChanged: function(page) {
        let pageno = $(page).data('page');
        let pagetotal = this.getPageCount();

        if (! $(page).hasClass('hidden')) {
            pdfThumbnails.setActivePage(pageno);
            $('#pageno').val(pageno);
            $('#pageno').attr('max', pagetotal);
            $('#pagecount').text('de ' + pagetotal);
        }
    },

    /** zoom to fit when the document is loaded and create the object if wanted to be downloaded */
    onDocumentReady: function () {
        pdfViewer.setZoom('fit');
        pdfViewer.pdf.getData().then(function(data) {
            document.querySelector('#filedownload').href = URL.createObjectURL(new Blob([data], {type: 'application/pdf'}));
            document.querySelector('#filedownload').target = '_blank';
        });
    }
});

/** Load the initial PDF file */
pdfViewer.loadDocument(PDFFILE).then(function() {
    document.querySelector('#filedownload').download = PDFFILE;
});

/** Create the thumbnails */
let pdfThumbnails = new PDFjsViewer($('.thumbnails'), {
    zoomFillArea: 0.7,
    onNewPage: function(page, i) {
        $('<div class="numbering">').text(i).appendTo(page);
        page.on('click', function() {
            pdfThumbnails.setActivePage(page.data('page'));                
            if (!pdfViewer.isPageVisible(page.data('page'))) {
                pdfViewer.scrollToPage(page.data('page'));
            } 
        })
    },
    onDocumentReady: function() {
        this.setZoom('fit');
    }
});

pdfThumbnails.setActivePage = function(pageno) {
    this.$container.find('.pdfpage').removeClass('selected');

    let $npage = this.$container.find('.pdfpage[data-page="' + pageno + '"]').addClass('selected');
    $('#hideselected').removeClass('pushed');
    if ($npage.hasClass('hidden'))
        $('#hideselected').addClass('pushed');

    if (!this.isPageVisible(pageno)) {
        this.scrollToPage(pageno);
    }
}.bind(pdfThumbnails);

pdfThumbnails.loadDocument(PDFFILE);

7. All default options and callbacks.

let pdfViewer = new PDFjsViewer($('.pdfpages'), {

    // Fraction of the area of the page that has to be visible to be considered that it is visible
    visibleThreshold: 0.5,

    // Number of pages to load appart from the currently visible one (to enable fast scrolling)
    extraPagesToLoad: 3,

    // The class used for each page (the div that wraps the content of the page)
    pageClass: "pdfpage",

    // The class used for the content of each page (the div that contains the page)
    contentClass: "content-wrapper",

    // Posible zoom values to iterate over using "in" and "out"
    zoomValues: [ 0.25, 0.5, 0.75, 1, 1.25, 1.50, 2, 4, 8 ],

    // Percentage of the container that will be filled with the page
    zoomFillArea: 0.95,

    // Enhance the resolution for the pages
    renderingScale: 1.5,

    // Function called when a document has been loaded and its structure has been created
    onDocumentReady: () => {},

    // Function called when a new page is created (it is binded to the object, and receives a jQuery object as parameter)
    onNewPage: (page, i) => {},

    // Function called when a page is rendered
    onPageRender: (page, i) => {},

    // Function called when the zoom level changes (it receives the zoom level)
    onZoomChange: (zoomlevel) => {},

    // Function called whenever the active page is changed (the active page is the one that is shown in the viewer)
    onActivePageChanged: (page, i) => {},

    // Function called to get the content of an empty page
    emptyContent: () => $('<div class="loader"></div>'),

    // Function called to obtain a page that shows an error when the document could not be loaded (returns a jQuery object)
    errorPage: () => {
      $(`<div class="placeholder"></div>`).addClass(this.settings.pageClass).append($(`<p class="m-auto"></p>`).text("could not load document"))
    },

});

8. API methods.

// load a PDF file and returns a promise
// the document can be either an url or a bin array.
pdfViewer.loadDocument(document);

// force re-init
pdfViewer.forceViewerInitialization();

// refresh all pages
pdfViewer.refreshAll();

// get the active page
pdfViewer.getActivePage();

// go to the first page
pdfViewer.first();

// go to the last page
pdfViewer.last();

// go to the next page
pdfViewer.next();

// back to the previous page
pdfViewer.prev();

// get the number of pages
pdfViewer.getPageCount();

// retrieve all the pages of the document
pdfViewer.getPages();

// go to a specific page
pdfViewer.scrollToPage(i);

// check if the page is visible
isPageVisible(i);

// set zoom level
// it is possible to use a float value which represents a fraction or a keyword 'in', 'out', 'width', 'height' or 'fit'
pdfViewer.setZoom(zoom);

// get the current zoom level
pdfViewer.getZoom();

// rotate the pages
pdfViewer.rotate(deg, accumulate = false);

Changelog:

v2.0.0 (2024-11-15)

  • change the cleaning method of the pages from empty() to html("") to try to solve one reported bug when generating some pages
  • use plain HTML objects in callbacks instead of jQuery objects. The library still depends on jQuery (or a jQuery-compatible lib), and is compatible with jQuery.
  • support for nojquery in the library (and prioritize the use over jQuery)

v1.1.2 (2024-02-17)

  • includes the renderingScale option to enhance the resolution for the pages
  • bugfixes

This awesome jQuery plugin is developed by dealfonso. For more Advanced Usages, please check the demo page or visit the official website.