# Terminal
rails g stimulus pdf_reader
yarn add pdfjs-dist
npm install -g node-gyp
# app/views/books/_book.html.erb
<div data-controller="pdf-reader" data-pdf-reader-url-value="<%= url_for(book.pdf) %>">
<div class="row justify-content-center">
<div class="col-12 col-md-8 d-flex justify-content-center">
<canvas data-pdf-reader-target="canvas" class="w-100"></canvas>
</div>
</div>
<div class="row justify-content-center mt-3">
<div class="col-12 col-md-8 d-flex flex-column flex-md-row justify-content-between">
<button
data-action="click->pdf-reader#prevPage"
class="btn btn-primary text-nowrap mb-2 mx-1 mb-md-0">
Previous Page
</button>
<input type="number" min="1"
data-pdf-reader-target="pageNumber"
data-action="change->pdf-reader#changePage"
class="form-control mb-2 mx-1 mb-md-0" />
<button
data-action="click->pdf-reader#nextPage"
class="btn btn-primary text-nowrap mb-2 mx-1 mb-md-0">
Next Page
</button>
</div>
</div>
</div>
# app/javascript/controllers/pdf_reader_controller.js
import { Controller } from "@hotwired/stimulus"
import { getDocument, GlobalWorkerOptions } from 'pdfjs-dist/build/pdf'
import pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry"
// Connects to data-controller="pdf-reader"
export default class extends Controller {
static targets = ["canvas", "pageNumber"]
static values = { url: String }
currentPage = 1
// this.currentPage
pdf = null
connect() {
GlobalWorkerOptions.workerSrc = pdfjsWorker
this.loadPdf()
}
async loadPdf() {
const loadingTask = getDocument(this.urlValue)
this.pdf = await loadingTask.promise
this.pageNumberTarget.value = this.currentPage
this.renderPage()
}
async renderPage() {
const page = await this.pdf.getPage(this.currentPage)
const viewport = page.getViewport({ scale: 1.75 })
const canvas = this.canvasTarget
canvas.width = viewport.width
canvas.height = viewport.height
const context = canvas.getContext("2d")
const renderContext = {
canvasContext: context,
viewport: viewport
}
await page.render(renderContext)
}
prevPage() {
if (this.currentPage > 1) {
this.currentPage -= 1
this.renderPage()
this.pageNumberTarget.value = this.currentPage
}
}
nextPage() {
if (this.currentPage < this.pdf.numPages) {
this.currentPage += 1
this.renderPage()
this.pageNumberTarget.value = this.currentPage
}
}
changePage() {
let requestedPage = Number(this.pageNumberTarget.value)
if (requestedPage > 0 && requestedPage <= this.pdf.numPages) {
this.currentPage = requestedPage
this.renderPage()
} else {
alert(`Invalid page number (Max: ${this.pdf.numPages})`)
this.pageNumberTarget.value = this.currentPage
}
}
}