文章目录
pdf预览组件
前情:项目要的急,就简单的能看pdf文件,没有其他功能
使用的 pdfjs-dist 版本号:2.5.207
接下来,直接上代码
封装组件
<template>
<template v-for="item in pageNum" :key="item">
<canvas :id="`pdf-canvas-${item}`" class="pdf-page" />
</template>
</template>
<script>
import { reactive, toRefs, nextTick, watchEffect } from 'vue'
import * as pdfjs from 'pdfjs-dist'
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';
import { Toast } from 'vant'
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker
export default {
name: 'PdfViewer',
props: {
url: {type: String, default: ''} // pdf文件路径
},
setup (props, { emit }) {
const state = reactive({
pageNum: 0,
pdfCtx: null
})
const resolvePdf = (url) => {
const loadingTask = pdfjs.getDocument(url)
loadingTask.promise.then(pdf => {
state.pdfCtx = pdf
state.pageNum = pdf.numPages
nextTick(() => {
renderPdf()
})
})
}
const renderPdf = (num = 1) => {
state.pdfCtx.getPage(num).then(page => {
const canvas = document.getElementById(`pdf-canvas-${num}`)
const ctx = canvas.getContext('2d')
const viewport = page.getViewport({ scale: 1 })
// 画布大小,默认值是width:300px,height:150px
canvas.height = viewport.height
canvas.width = viewport.width
// 画布的dom大小, 设置移动端,宽度设置铺满整个屏幕
const clientWidth = document.body.clientWidth
canvas.style.width = clientWidth + 'px'
// 根据pdf每页的宽高比例设置canvas的高度
canvas.style.height = clientWidth * (viewport.height / viewport.width) + 'px'
page.render({
canvasContext: ctx,
viewport
})
if (num < state.pageNum) {
renderPdf(num + 1)
} else {
emit('onRendered')
Toast.clear(); // 取消加载loading
}
})
}
watchEffect(() => {
if (props.url) {
// 展示加载loading
Toast.loading({
message: '加载中...',
overlay: true,
forbidClick: true,
duration: 0,
});
resolvePdf(props.url)
}
})
return {
...toRefs(state)
}
}
}
</script>
组件注册
这里是全局注册组件,main.js中添加下面代码 引入组件
详细方法可看我的文章vue3.0全局注册自定义组件及使用
import PdfViewer from "./components/PdfViewer.vue"
app.component('PdfViewer', PdfViewer)
使用
pdfUrl中传入pdf文件地址
<PdfViewer :url="pdfUrl"></PdfViewer>
参考文章
https://www.52pojie.cn/thread-1391808-1-1.html
增加缩放功能(2021-12-15更新)
有了些空闲时间,想完善下pdf预览组件
看了网上的文章,都需要时间慢慢研究下
空闲时间不多,我就先用我想到的方法,实现下功能叭~
增加了工具栏,有页面缩放功能,和当前页页码展示
缩放功能关键点:
pdfjs的scale设置为需要放大的最大值;
使用css进行缩放
组件
<template>
<!-- *发挥想象的工具栏 -->
<div class="tool-bar" v-show="totalPageNum != 0">
<div class="pageNum">{{currentPageNum}} / {{totalPageNum}}页</div>
<span class="txt">放大</span>
<div class="box">
<div class="mask" v-show="zoom == maxZoom"></div>
<van-icon name="plus" size="18" @click="zoomChange('plus')"/>
</div>
<span class="txt">缩小</span>
<div class="box">
<div class="mask" v-show="zoom == minZoom"></div>
<van-icon name="minus" size="18" @click="zoomChange('minus')"/>
</div>
</div>
<!-- 工具栏 end -->
<div class="pdf-viewer" @scroll="onScroll">
<template v-for="item in totalPageNum" :key="item">
<canvas class="pdf-item" :id="`pdf-canvas-${item}`" :style="{zoom: zoom/10}" />
</template>
</div>
</template>
<script>
import { reactive, toRefs, nextTick, watchEffect } from 'vue'
import * as pdfjs from 'pdfjs-dist'
// import {GlobalWorkerOptions, getDocument} from 'pdfjs-dist'
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';
import { Toast } from 'vant'
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker
export default {
name: 'PdfViewer',
props: {
url: {type: String, default: ''} // pdf文件路径
},
setup (props, { emit }) {
const state = reactive({
pdfCtx: null,
currentPageNum: 0, // 当前页
totalPageNum: 0,
zoom: 10, // 目前暂时采用css方式缩放页面
minZoom: 10, // 缩放最小值 一倍
maxZoom: 50, // 缩放最大值 五倍
})
const resolvePdf = (url) => {
const loadingTask = pdfjs.getDocument(url)
loadingTask.promise.then(pdf => {
state.pdfCtx = pdf
state.totalPageNum = pdf.numPages
state.currentPageNum = 1
// 动态计算scale
pdf.getPage(1).then(res => {
let boxWidth = document.body.clientWidth - 20
const [x1, , x2] = res._pageInfo.view;
const pageWidth = x2 - x1;
state.scale = boxWidth*(state.maxZoom/10) / pageWidth
})
nextTick(() => {
renderPdf()
})
})
}
const renderPdf = (num = 1) => {
state.pdfCtx.getPage(num).then(page => {
const canvas = document.getElementById(`pdf-canvas-${num}`)
const ctx = canvas.getContext('2d')
const viewport = page.getViewport({ scale: state.scale })
// 画布大小,默认值是width:300px,height:150px
canvas.width = viewport.width
canvas.height = viewport.height
// 画布的dom大小, 设置移动端,宽度设置铺满整个屏幕
const clientWidth = document.body.clientWidth - 20
canvas.style.width = clientWidth + 'px'
// 根据pdf每页的宽高比例设置canvas的高度
canvas.style.height = clientWidth * (viewport.height / viewport.width) + 'px'
page.render({
canvasContext: ctx,
viewport
})
if (num < state.totalPageNum) {
renderPdf(num + 1)
} else {
emit('onRendered')
Toast.clear();
}
})
}
const zoomChange = (value) => {
state.zoom = value == 'plus' ? state.zoom + 5 : state.zoom - 5
let pdfViewer = document.getElementsByClassName('pdf-viewer')[0]
let pages = document.getElementsByClassName('pdf-item')
pdfViewer.scrollTop = pages[state.currentPageNum-1].offsetTop*state.zoom/10
}
const onScroll = (e) => {
let pages = document.getElementsByClassName('pdf-item')
for (let i=0; i<state.totalPageNum; i++) {
let offset = e.target.offsetHeight/2-100 // 距顶部的距离(当前页在滚动到页面可视区的哪个位置时更改currentPageNum)
if (e.target.scrollTop >= pages[i].offsetTop*state.zoom/10 - offset ) {
if ((pages[i+1] && e.target.scrollTop < pages[i+1].offsetTop*state.zoom/10 - offset) || !pages[i+1]) {
state.currentPageNum = i+1
}
}
}
}
watchEffect(() => {
if (props.url) {
Toast.loading({
message: '文件加载中...',
overlay: true,
forbidClick: true,
duration: 0,
});
resolvePdf(props.url)
}
})
return {
...toRefs(state),
zoomChange,
onScroll
}
}
}
</script>
<style lang="less" scoped>
.pdf-viewer {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: 10px 10px 50px;
overflow: scroll;
background-color: #f5f5f5;
}
// 放飞自我的工具栏
.tool-bar {
z-index: 2;
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 45px;
line-height: 45px;
color: #fff;
padding: 0 20px;
background-color: rgba(85, 85, 85, 0.6);
display: flex;
justify-content: flex-end;
.pageNum {
flex: 1;
font-size: 17px;
}
.txt {
font-size: 16px;
padding-right: 5px;
margin-left: 20px;
}
.box {
position: relative;
margin-top: 5px;
width: 35px;
height: 35px;
line-height: 35px;
text-align: center;
background-color: #3476FE;
border-radius: 50%;
.van-icon {
vertical-align: middle;
font-weight: bold;
}
.mask {
z-index: 1;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(109, 109, 109, .7);
border-radius: 50%;
}
}
// 工具栏 end
}
</style>
使用
父级要有一个整屏高的外层
<div style="position: relative;min-height: calc( 100vh - 50px);">
...
<PdfViewer :url="pdfUrl"></PdfViewer>
...
</div>