1. 效果展示
2. pdfView.vue編寫
<template>
<div class="load-proccess-box">加載中 {{ loadProccess.toFixed(0) }}%</div>
<div class="pdf-viewer-box" v-show="loadProccess == 100">
<div class="container" ref="container" @scroll="scroll">
<pdf-list :ref="genPageElList" :scale="scale" :pdf="item.pdf" :page="item.page" v-for="item in pageList" :key="item.page"></pdf-list>
<div class="error-box" v-if="isError">加載資源失敗...</div>
</div>
<div class="tool-box">
<div class="tool-list page-up" @click="pageUp">
<span class="iconfont icon-xiangshang4"></span>
</div>
<div class="tool-list page-down" @click="pageDown">
<span class="iconfont icon-xiangxia4"></span>
</div>
<div class="page-box">
<form @submit.prevent="" action=""><input @keyup.enter="enterPage" v-model="inputPageNo" @blur="blurPage" /></form>
/ {{ pages }}
</div>
<div class="tool-list page-narrow" @click="narrow">
<span class="iconfont icon-suoxiao1"></span>
</div>
<div class="tool-list page-amplify" @click="amplify">
<span class="iconfont icon-fangda1"></span>
</div>
</div>
</div>
</template>
<script>
import PDFJS from 'pdfjs-dist'
import PdfList from './PdfList'
PDFJS.GlobalWorkerOptions.workerSrc = 'pdfjs-dist/build/pdf.worker.js'
export default {
data() {
return {
loadProccess: 0,
pageNo: 1,
inputPageNo: 1,
pages: 0,
container: null,
pdf: null,
pageList: [],
pageElList: [],
scale: 10,
pageHeightList: null,
isError: false,
resizeTimer: null
}
},
components: {
PdfList
},
mounted() {
this.init()
window.addEventListener('onorientationchange' in window ? 'orientationchange' : 'resize', this.resize)
},
unmounted() {
window.removeEventListener('onorientationchange' in window ? 'orientationchange' : 'resize', this.resize)
if (this.resizeTimer) {
clearTimeout(this.resizeTimer)
}
},
beforeUpdate() {
this.pageElList = []
},
watch: {
pageNo() {
this.inputPageNo = this.pageNo
},
$route() {
console.log('router')
this.init()
}
},
methods: {
// 初始化
async init() {
try {
const url = 'http://image.damoxueyuan.com/files/6%E3%80%81%E5%8F%91%E8%A1%8C%E6%96%B9%E8%B5%84%E6%96%99.pdf'
// 重置變量
this.pageNo = 1
this.pageList = []
this.pageElList = []
this.isError = false
this.loadProccess = 0
// 加載PDF
let loadingTask = PDFJS.getDocument(url)
loadingTask.onProgress = (progress) => {
let percent = parseInt((progress.loaded / progress.total) * 100)
percent = percent >= 100 ? 100 : percent
this.loadProccess = percent
}
const pdf = await loadingTask.promise
this.pdf = pdf
this.pages = this.pdf.numPages
this.container = this.$refs.container
this.handlePdfPage()
} catch (err) {
console.error(err)
this.isError = true
}
},
// 生成pdf page數(shù)組
handlePdfPage() {
for (let i = 1; i <= this.pages; i++) {
this.pageList.push({
page: i,
pdf: this.pdf
})
}
// await this.renderPdf(this.pdf, 5)
},
genPageElList(el) {
this.pageElList.push(el)
},
// 初始化每頁的高度,需要注意pdf每頁的高度不一樣砚蓬,所以這里手動記錄每頁的高度方便定位
initPageHeightList() {
let height = 0
this.pageHeightList = [0]
for (let i = 0; i < this.pageElList.length - 1; i++) {
let el = this.pageElList[i]
let elHeight = el.getHeight()
this.pageHeightList.push(height + elHeight / 2)
height += elHeight
}
},
// 定位當前頁數(shù)
scroll() {
if (this.pageHeightList == null) {
this.initPageHeightList()
}
let offsetTop = this.$refs.container.scrollTop
for (let i = 0; i < this.pageHeightList.length; i++) {
if (offsetTop >= this.pageHeightList[i]) {
this.pageNo = i + 1
}
}
},
// 向上翻頁
pageUp() {
if (this.pageNo <= 1) {
return
}
this.pageElList[this.pageNo - 2].$el.scrollIntoView({
block: 'center',
behavior: 'smooth'
})
},
// 向下翻頁
pageDown() {
if (this.pageNo === this.pages) {
return
}
this.pageElList[this.pageNo].$el.scrollIntoView({
block: 'center',
behavior: 'smooth'
})
},
blurPage() {
this.inputPageNo = this.pageNo
},
// 輸入頁數(shù)進行定位
enterPage() {
try {
if (this.inputPageNo > this.pages || this.inputPageNo < 1) {
this.inputPageNo = this.pageNo
return
}
this.pageElList[this.inputPageNo - 1].$el.scrollIntoView({
block: 'center',
behavior: 'smooth'
})
this.pageNo = this.inputPageNo
} catch (err) {
console.error(err)
this.inputPageNo = this.pageNo
}
},
// 縮小
narrow() {
if (this.scale == 5) {
return
}
this.scale = this.scale - 1
},
// 放大
amplify() {
if (this.scale == 15) {
return
}
this.scale = this.scale + 1
},
// 用戶切換豎屏橫屏時本昏,重新計算寬高
resize() {
this.resizeTimer = setTimeout(async () => {
for (let i = 0; i < this.pageElList.length; i++) {
await this.pageElList[i].resize()
}
this.initPageHeightList()
}, 200)
}
}
}
</script>
<style lang="less" scoped>
.container {
height: calc(100vh - 50px);
overflow-y: auto;
padding-bottom: 50px;
.error-box {
height: calc(100vh - 50px);
display: flex;
align-items: center;
justify-content: center;
}
}
.tool-box {
position: fixed;
height: 50px;
bottom: 0;
width: 100%;
background: #323639;
display: flex;
.tool-list {
display: flex;
align-items: center;
justify-content: center;
padding: 10px 20px;
span {
color: #fff;
}
}
.page-box {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
input {
display: inline-block;
background: rgba(0, 0, 0, 0.5);
border: none;
max-width: 50px;
padding: 0 5px;
margin-right: 5px;
&::-webkit-search-cancel-button {
-webkit-appearance: none;
}
}
}
}
.load-proccess-box{
display: flex;
align-items: center;
justify-content: center;
height: calc(100vh - 50px);
}
</style>
3. PdfList.vue編寫
<template>
<div class="page-list" ref="pageList">
<div class="page-wrapper card">
<div class="canvas-wrapper">
<canvas ref="canvas" :style="{ transform: `scale(${scale / 10})` }" v-show="isInit"></canvas>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
pdf: {
type: Object,
default: null
},
page: {
type: Number,
default: null
},
scale: {
type: Number,
default: 10
}
},
data() {
return {
isInit: false,
rate: 1
}
},
mounted() {
this.init()
},
methods: {
init() {
this.renderPdf()
},
async resize() {
await this.$nextTick()
await this.refreshShow()
},
// 獲取當前高度
getHeight() {
return this.$refs.pageList.clientHeight
},
// 渲染pdf
async renderPdf() {
await this.$nextTick()
this.isInit = false
let page = await this.pdf.getPage(this.page)
let scale = 1
let viewPort = page.getViewport(scale)
let canvas = this.$refs.canvas
canvas.width = viewPort.width
canvas.height = viewPort.height
let context = canvas.getContext('2d')
let renderContext = {
canvasContext: context,
viewport: viewPort
}
await page.render(renderContext).promise
this.rate = viewPort.height / viewPort.width
this.refreshShow()
},
// 重新設置canvas寬高
refreshShow() {
if (!this.$refs.pageList) {
return
}
let canvas = this.$refs.canvas
let width = this.$refs.pageList.clientWidth - 60
canvas.style.width = width + 'px'
canvas.style.height = width * this.rate + 'px'
this.isInit = true
}
}
}
</script>
<style lang="less" scoped>
.page-list {
padding: 10px;
}
.canvas-wrapper {
overflow: hidden;
position: relative;
}
</style>
如果編譯時遇到 Module parse failed: Unexpected token 錯誤笆檀,可以將版本降低至 2.3.200 版本