首先安装pdfjs-dist插件
bash
npm install pdfjs-dist
然后引入插件和样式
js
import * as pdfjsLib from 'pdfjs-dist'
import 'pdfjs-dist/web/pdf_viewer.css'
pdf.js 在解析和渲染 PDF 时,计算量比较大。为了避免阻塞主线程(UI 卡顿),需要把PDF的解析任务放在Web Worker里执行。
所以我们需要手动配置pdfjsWorker。
js
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry'
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker
如果不设置在浏览器端运行时,pdf.js 会报错
error
Warning: Setting up fake worker.
这意味着它找不到 worker 文件,只能退回到“假 worker”模式(实际还是在主线程执行),性能会下降。
接下来我们写一下渲染pdf的核心方法
vue
<template>
<div
ref="pdfContainer"
class="pdf-viewer"
/>
</template>
js
async function renderPdfFile() {
const container = this.$refs.pdfContainer
container.innerHTML = ''
const loadingTask = pdfjsLib.getDocument(this.src)
const pdf = await loadingTask.promise
for (const pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
const page = await pdf.getPage(pageNum)
const viewport = page.getViewport({ scale: 1 })
const canvas = document.createElement('canvas')
canvas.style = 'margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);'
const context = canvas.getContext('2d')
const dpr = window.devicePixelRatio || 1
canvas.width = viewport.width * dpr
canvas.height = viewport.height * dpr
canvas.style.width = `${viewport.width}px`
canvas.style.height = `${viewport.height}px`
const transform = [dpr, 0, 0, dpr, 0, 0]
const renderContext = {
canvasContext: context,
transform,
viewport
}
await page.render(renderContext).promise
container.appendChild(canvas)
}
}
上述方法主要在做以下几点
- 加载PDF:使用pdfjsLib异步加载指定URL的PDF文件
- 遍历页面:循环处理PDF中的每一页
- 创建画布:为每页创建canvas元素并设置样式
- 适配分辨率:根据设备像素比调整画布尺寸
- 渲染页面:将PDF页面渲染到canvas上
- 添加到容器:将渲染好的canvas添加到指定容器中显示
渲染PDF的工作基本完成,但有时候我们的PDF文件是加密的怎么办呢?
pdfjsLib.getDocument提供了第二种传参方式,接受一个对象,对象中传url和password属性,并提供了onPassword事件。
js
const loadingTask = pdfjsLib.getDocument({
url,
password
})
js
loadingTask.onPassword = (updatePassword, reason) => {
this.passwordCallback = updatePassword
this.message = reason === pdfjsLib.PasswordResponses.NEED_PASSWORD
? '本文档设置了密码保护,请输入密码。'
: '密码错误,请重新输入。'
}
当pdfjsLib加载PDF文件时,文件需要输入密码或者密码错误时会触发onPassword回调
最后我们封装一个预览PDF的vue组件。组件接受一个PDF文件的url。组件中用一个modal弹框组件,用来输入密码。
vue
<template>
<div>
<div
ref="pdfContainer"
class="pdf-viewer"
/>
<e-modal
title="需要密码"
:width="420"
v-model="showPasswordModal"
:closable="false"
>
<div style="line-height: 36px;">{{ message }}</div>
<e-input
type="password"
password
:clearable="false"
v-model="password"
@on-enter="handleConfirm"
/>
<template slot="footer">
<div style="display: flex;justify-content: flex-end;">
<e-button type="primary" @click="handleConfirm">确认</e-button>
</div>
</template>
</e-modal>
</div>
</template>
<script>
import * as pdfjsLib from 'pdfjs-dist'
import 'pdfjs-dist/web/pdf_viewer.css'
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry'
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker
export default {
name: 'PdfViewer',
props: {
url: String
},
data() {
return {
showPasswordModal: false,
password: '',
message: '',
passwordCallback: null
}
},
watch: {
url: {
handler() {
this.$nextTick(() => {
this.renderPdfFile()
})
},
immediate: true
}
},
methods: {
handleConfirm() {
if (this.passwordCallback && this.password) {
const cb = this.passwordCallback
this.passwordCallback = null
cb(this.password)
}
},
async renderPdfFile(password = null) {
const container = this.$refs.pdfContainer
container.innerHTML = ''
try {
const loadingTask = pdfjsLib.getDocument({
url: this.url,
password
})
loadingTask.onPassword = (updatePassword, reason) => {
this.password = ''
this.passwordCallback = updatePassword
this.message = reason === pdfjsLib.PasswordResponses.NEED_PASSWORD
? '本文档设置了密码保护,请输入密码。'
: '密码错误,请重新输入。'
if (!this.showPasswordModal) {
this.showPasswordModal = true
}
}
const pdf = await loadingTask.promise
if (this.showPasswordModal) {
this.showPasswordModal = false
}
for (const pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
const page = await pdf.getPage(pageNum)
const viewport = page.getViewport({ scale: 1 })
const canvas = document.createElement('canvas')
canvas.style = 'margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);'
const context = canvas.getContext('2d')
const dpr = window.devicePixelRatio || 1
canvas.width = viewport.width * dpr
canvas.height = viewport.height * dpr
canvas.style.width = `${viewport.width}px`
canvas.style.height = `${viewport.height}px`
const transform = [dpr, 0, 0, dpr, 0, 0]
const renderContext = {
canvasContext: context,
transform,
viewport
}
await page.render(renderContext).promise
container.appendChild(canvas)
}
} catch (err) {
console.error('pdf加载失败', err)
}
}
}
}
</script>
<style lang="less" scoped>
.pdf-viewer {
width: 100%;
max-height: 100vh;
overflow-y: auto;
background-color: #f5f5f5;
padding: 10px;
display: flex;
flex-direction: column;
align-items: center;
}
</style>
效果图
