Adding PDF viewer to Eleventy
I wanted to add a PDF viewer to Eleventy using PDF.js.
At first, I installed PDF.js from npm. However, the npm version only works on Firefox and Chrome. To get a version that works on Safari, I needed to use the legacy build version.
I cloned PDF.js repo and installed dependencies.
git clone https://github.com/mozilla/pdf.js.git
cd pdf.js
npm install
Then I built the legacy version.
npx gulp generic-legacy
This generated pdf.js
and pdf.worker.js
in the build/generic-legacy/build/
directory. I copied pdf.js
and pdf.worker.js
to /public/lib/pdfjs/generic-legacy/build
.
Then I put the code to create a viewer in public/lib/pdfviewer.js
. The code is based on PDF.js Prev/Next example and mobile viewer example.
// set workerSrc property
pdfjsLib.GlobalWorkerOptions.workerSrc =
"/lib/pdfjs/generic-legacy/build/pdf.worker.mjs";
let pdfDoc = null;
let pageNum = 1;
let pageRendering = false;
let pageNumPending = null;
let scale = 1;
let canvas = document.getElementById("the-canvas");
let ctx = canvas.getContext("2d");
const DEFAULT_SCALE_DELTA = 1.1;
const MIN_SCALE = 0.25;
const MAX_SCALE = 10.0;
/**
* Get page info from document, resize canvas accordingly, and render page.
*/
function renderPage(num) {
if (canvas == null) return;
pageRendering = true;
// Using promise to fetch the page
pdfDoc.getPage(num).then(function (page) {
var viewport = page.getViewport({ scale: scale });
// Support HiDPI-screens.
var outputScale = window.devicePixelRatio || 1;
canvas.width = Math.floor(viewport.width * outputScale);
canvas.height = Math.floor(viewport.height * outputScale);
canvas.style.width = Math.floor(viewport.width) + "px";
canvas.style.height = Math.floor(viewport.height) + "px";
var transform =
outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;
// Render PDF page into canvas context
var renderContext = {
canvasContext: ctx,
transform: transform,
viewport: viewport,
};
var renderTask = page.render(renderContext);
// Wait for rendering to finish
renderTask.promise.then(function () {
pageRendering = false;
if (pageNumPending !== null) {
// New page rendering is pending
renderPage(pageNumPending);
pageNumPending = null;
}
});
});
// Update page counters
document.getElementById("page_num").textContent = num;
}
/**
* If another page rendering in progress, waits until the rendering is
* finished. Otherwise, executes rendering immediately.
*/
function queueRenderPage(num) {
if (pageRendering) {
pageNumPending = num;
} else {
renderPage(num);
}
}
/**
* Displays previous page.
*/
function onPrevPage() {
if (pageNum <= 1) {
return;
}
pageNum--;
queueRenderPage(pageNum);
}
/**
* Displays next page.
*/
function onNextPage() {
if (pageNum >= pdfDoc.numPages) {
return;
}
pageNum++;
queueRenderPage(pageNum);
}
/**
* Zoom in
*/
function pdfViewZoomIn() {
let newScale = scale;
newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
newScale = Math.ceil(newScale * 10) / 10;
newScale = Math.min(MAX_SCALE, newScale);
scale = newScale;
queueRenderPage(pageNum);
}
/**
* Zoom out
*/
function pdfViewZoomOut() {
let newScale = scale;
newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
newScale = Math.floor(newScale * 10) / 10;
newScale = Math.max(MIN_SCALE, newScale);
scale = newScale;
queueRenderPage(pageNum);
}
/**
* Setup event handlers
*/
function setupEventHandlers() {
let prevEl = document.getElementById("prev");
if (prevEl) prevEl.addEventListener("click", onPrevPage);
let nextEl = document.getElementById("next");
if (nextEl) nextEl.addEventListener("click", onNextPage);
let zoomInEl = document.getElementById("zoomIn");
if (zoomInEl) zoomInEl.addEventListener("click", pdfViewZoomIn);
let zoomOutEl = document.getElementById("zoomOut");
if (zoomOutEl) zoomOutEl.addEventListener("click", pdfViewZoomOut);
}
export async function init(url) {
setupEventHandlers();
// download pdf
var loadingTask = pdfjsLib.getDocument(url);
pdfDoc = await loadingTask.promise;
// set page count
let countEl = document.getElementById("page_count");
if (countEl) countEl.textContent = pdfDoc.numPages;
// render first page
renderPage(pageNum);
}
I added a template file in _includes/pdfviewer.njk
. pdfviewer.njk
calls the init()
from pdfviewer.js
with pdfUrl
and pdfScale
arguments.
<style>
{% include "css/pdfviewer.css" %}
</style>
<section class="pdfviewer">
<div>
<button id="prev" type="button" title="previous page">Previous</button>
<button id="next" type="button" title="next page">Next</button>
<button id="zoomIn" type="button" title="zoom in">+</button>
<button id="zoomOut" type="button" title="zoom out">-</button>
<span
>Page: <span id="page_num"></span> / <span id="page_count"></span
></span>
</div>
<canvas id="the-canvas" style="border: 1px solid black;"></canvas>
</section>
<script src="/lib/pdfjs/generic-legacy/build/pdf.mjs" type="module"></script>
<script type="module">
import {init} from '/pdfviewer.js'
init('{{pdfUrl}}', {{pdfScale}})
</script>
Then in the file I want to add a pdf, I included pdfviewer.njk
and passed in the pdfUrl
.
{% set pdfUrl = '/assets/pdfs/pcc-portfolio/inat_la_river.pdf' %}
{% include "pdfviewer.njk" %}
To adjust the size of the pdf, I passed in pdfScale
argument. If it is not set, the value defaults to 1.
{% set pdfUrl = '/assets/pdfs/pcc-portfolio/inat_la_river.pdf' %}
{% set pdfScale = '.4' %}
{% include "pdfviewer.njk" %}
Here's a PDF.
- Previous: Adding Leaflet maps to Eleventy
- Next: CSS Nine Years Later