"pdfjs-dist": "^4.1.392",
"lodash-es": "^4.17.21",
"vite-plugin-top-level-await": "^1.4.1",
"vue": "^3.4.15",
TypeScript
<template>
<div class="pdf-container">
<canvas :id="`pdf-canvas`" />
</div>
<div class="page-tool">
<div class="page-tool-item" @click="lastPage">上一页</div>
<div class="page-tool-item" @click="nextPage">下一页</div>
<div class="page-tool-item">{{state.pageNum}}/{{state.numPages}}</div>
<div class="page-tool-item" @click="pageZoomOut">放大</div>
<div class="page-tool-item" @click="pageZoomIn">缩小</div>
</div>
</template>
<script lang="ts" setup>
import * as PDFJS from 'pdfjs-dist'
import * as PdfWorker from 'pdfjs-dist/build/pdf.worker.mjs'
import { nextTick, ref, Ref, watch, reactive, computed } from 'vue'
import { isEmpty, debounce } from 'lodash-es'
(window as any).PdfWorker = PdfWorker
const props : any = defineProps({
pdf: {
required: true
}
})
const state = reactive({
source: "", //预览pdf文件地址
pageNum: 1, //当前页面
scale: 1, // 缩放比例
numPages: 0, // 总页数
showpdf: true,
});
const scaleFun = computed(() => 'transform:scale(${state.scale})')
let pdfDoc : any = null
const pdfPages : Ref = ref(0)
const pdfScale : Ref = ref(1.3)
const loadFile = async (url : any) => {
// 设定pdfjs的 workerSrc 参数
// PDFJS.GlobalWorkerOptions.workerSrc = PdfWorker
const loadingTask = PDFJS.getDocument(url)
loadingTask.promise.then(async (pdf : any) => {
pdfDoc = pdf // 保存加载的pdf文件流
state.numPages = pdfPages.value = pdfDoc.numPages // 获取pdf文件的总页数
await nextTick(() => {
renderPage(1) // 将pdf文件内容渲染到canvas
})
}).catch((error : any) => {
//可以用自己组件库弹出提示框
console.log(error)
})
}
const renderPage = (num : any) => {
pdfDoc.getPage(num).then((page : any) => {
page.cleanup()
const canvas : any = document.getElementById(`pdf-canvas`)
if (canvas) {
const ctx = canvas.getContext('2d')
const dpr = window.devicePixelRatio || 1
const bsr = ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio ||
1
const ratio = dpr / bsr
const viewport = page.getViewport({ scale: pdfScale.value })
canvas.width = viewport.width * ratio
canvas.height = viewport.height * ratio
canvas.style.width = viewport.width + 'px'
canvas.style.height = viewport.height + 'px'
ctx.setTransform(ratio, 0, 0, ratio, 0, 0)
const renderContext = {
canvasContext: ctx,
viewport: viewport
}
page.render(renderContext)
state.pageNum = num;
// if (num < pdfPages.value) {
// renderPage(num + 1)
// }
}
})
}
function lastPage() {
if (state.pageNum > 1) {
renderPage(state.pageNum - 1);
}
}
function nextPage() {
if (state.pageNum < state.numPages) {
renderPage(state.pageNum + 1);
}
}
function pageZoomOut() {
if (pdfScale.value < 5) {
pdfScale.value += 0.1;
renderPage(state.pageNum);
}
}
function pageZoomIn() {
if (pdfScale.value > 0.5) {
pdfScale.value -= 0.1;
renderPage(state.pageNum);
}
}
const debouncedLoadFile = debounce((pdf : any) => loadFile(pdf), 1000)
watch(() => props.pdf, (newValue : any) => {
!isEmpty(newValue) && debouncedLoadFile(newValue)
}, {
immediate: true
})
</script>
<style scoped>
.page-tool {
position: absolute;
bottom: 35px;
padding-left: 15px;
padding-right: 15px;
display: flex;
align-items: center;
background: rgb(66, 66, 66);
color: white;
border-radius: 19px;
z-index: 100;
cursor: pointer;
margin-left: 50%;
transform: translateX(-50%);
}
.page-tool-item {
padding: 8px 15px;
padding-left: 10px;
cursor: pointer;
}
.pdf-container {
width: 1000px;
height: 600px;
resize: both;
overflow: auto;
/* 启用滚动条 */
}
canvas {
width: 100%;
pointer-events: none;
max-height: 100vh;
/* 设置最大高度为视口高度 */
}
</style>
效果: