1、使用插件
pdfjs-dist
版本: 2.2.228, 这个版本目前来看bug少一些
pnpm install pdfjs-dist@2.2.228
2、使用场景
数据来源为pdf,且pdf文件里有很多页,需要在外面自己分页展示出来,并且可以自己放大缩小和拖动
最终效果图
2.1、定义需要的数据
pageControl: { // 页面参数
curPage: 1, // 当前页码
pages: 0, // 总共多少页
scale: 100, // 用于展示的缩放比例, 默认为100,
beforeScale: 1, // 真实缩放比例,最大缩放 2,最小缩放 0.2,这两个比例为 100:1
rotation: 0 // 当前旋转参数
},
pdfDoc: null, // pdf对象
draggable: null // 拖动dom
2.2、页面结构
preview.vue
v-drag 为自定义拖拽指令,用于一般dom在父节点范围内拖动,想 了解的在我另一篇博客查看
pdf想进行预览,有两种方式,转canvas和转svg(只修改renderPage函数)
下面是pdf转svg方式
<template>
<div ref="preview" class="preview">
<div
:id="`img-box${item.id}`"
v-drag
class="img-box"
@mousewheel="(e) => scaleDom(e, 'wheel')"
/>
<footer-ai
:mode="item.id"
:page="pageControl"
@scaleControl="scaleControl"
@pageChange="pageChange"
/>
</div>
</template>
<script>
import PDFJS from 'pdfjs-dist';
import workerSrc from 'pdfjs-dist/build/pdf.worker.entry';
PDFJS.workerSrc = workerSrc;
import FooterAi from './component/footerAi.vue' // eslint-disable-line
export default {
name: 'Preview',
components: {
FooterAi
},
props: {
item: {
type: Object,
default: () => {}
}
},
data() {
return {
pageControl: { // 页码参数
curPage: 1, // 当前页码
pages: 0, // 总共页码
scale: 100, // 转换后缩放比例, 默认为100, 最大缩放 2,最小缩放 0.2
beforeScale: 1, // 转换前缩放比例
rotation: 0 // 当前旋转参数
},
pdfDoc: null,
draggable: null // 图片dom
}
},
mounted() {
this.$nextTick(() => {
this.draggable = document.getElementById(`img-box${this.item.id}`)
this.loadFile('XXX.pdf')
})
},
methods: {
/**
* pdf渲染
* @param {*} num
*/
renderPage(num) {
// 返回单页内容实例(页面索引) pdf.getPage(index)
this.pdfDoc.getPage(num).then((page) => {
if (this.draggable.hasChildNodes()) {
this.draggable.removeChild(this.draggable.firstChild)
}
const viewport = page.getViewport({ scale: this.pageControl.beforeScale })
const container = document.createElement('div')
container.id = 'yxp_svg_' + num
container.className = 'pageContainer'
container.style.width = viewport.width + 'px'
container.style.height = viewport.height + 'px'
this.draggable.appendChild(container)
return page.getOperatorList().then(function(opList) {
const svgGfx = new PDFJS.SVGGraphics(page.commonObjs, page.objs)
return svgGfx.getSVG(opList, viewport).then(function(svg) {
container.appendChild(svg)
})
})
})
},
/**
* 获取整个pdf文档
* @param {*} url
*/
loadFile(url) {
PDFJS.getDocument({
url,
cMapPacked: true
}).promise.then((pdf) => {
this.pdfDoc = pdf
this.pageControl.pages = this.pdfDoc.numPages
this.$nextTick(() => {
this.pageControl.curPage = 1
this.renderPage(this.pageControl.curPage)
})
}, (err) => {
if (err.name === 'MissingPDFException') {
this.$message.warn('无效的PDF链接')
}
})
},
pageChange(item) {
this.$nextTick(() => {
this.pageControl.curPage = item.page
this.renderPage(this.pageControl.curPage)
})
},
/**
* 当前缩放比例控制
* @param {*} val
*/
scaleControl(val) {
if (val) {
this.scaleDom(val, 'click')
} else {
this.pageControl.scale = 100
this.pageControl.beforeScale = 1
this.renderPage(this.pageControl.curPage)
// this.draggable.style.transform = 'scale(1)'
}
},
/**
* 鼠标滑轮事件
* @param {*} e
*/
scaleDom(e, type = 'wheel') {
// parseFloat((this.draggable.style.transform || `scale(1)`).replace(/[^0-9.]/gi, ''))
let scaleReal = this.pageControl.beforeScale
const size = type === 'wheel' ? e.wheelDelta / 1200 : parseFloat(e / 10)
scaleReal += size;
if (scaleReal >= 0.18 && scaleReal <= 2) { // 不能直接取 0.2, 因为浏览器不同,可能每次size都不能刚好为 0.1
this.pageControl.beforeScale = Number(scaleReal.toFixed(2))
this.pageControl.scale = Number((Number(scaleReal.toFixed(2)) * 100).toFixed(0))
// this.draggable.style.transform = `scale(${scaleReal})`
this.renderPage(this.pageControl.curPage)
}
}
}
}
</script>
<style lang="scss" scoped>
.preview{
position: relative;
flex: 1 0 50%;
background-color: #606266;
overflow: hidden;
.img-box{
height: 100%;
position: absolute;
}
}
</style>
pdf转canvas方式(只有renderPage方法有区别)
// canvas 绘制 PDF
renderPage(num) {
// 返回单页内容实例(页面索引) pdf.getPage(index)
this.pdfDoc.getPage(num).then((page) => {
const canvas = document.getElementById('the-canvas' + num)
const ctx = canvas.getContext('2d');
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
const dpr = window.devicePixelRatio || 1
const bsr = ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1
const ratio = dpr / bsr
// 返回页面内容(比例) page.getViewport({scale:2.0})语法改这么写
const viewport = page.getViewport({ scale: this.pageControl.beforeScale, rotation: this.pageControl.rotation });// 这是让pdf文件的大小等于视口的大小
canvas.width = viewport.width * ratio
canvas.height = viewport.height * ratio// 这里会进行压缩,解决模糊问题
canvas.style.width = viewport.width + 'px'
canvas.style.height = viewport.height + 'px'
const renderContext = {
canvasContext: ctx,
viewport: viewport,
transform: [ratio, 0, 0, ratio, 0, 0]// 这里会进行放大,解决模糊问题
}
const that = this
page.render(renderContext).promise.then(function() {
if (that.isDestory) {
page.getTextContent()
if (that.pageControl.pages > num) {
that.renderPage(num + 1)
}
}
});
})
}
footerAi.vue
<template>
<div class="footer-ai">
<article>{{ mode | modefilter }}</article>
<div class="page-select">
<div :class="['select-btn', {'select-btn-disable': banPrev}]">
<i
:class="['el-icon-arrow-left', {'ban-btn':banPrev}]"
@click="handlePage('prev')"
/>
</div>
<span class="qti-number">
<el-input
v-model="currentPage"
size="mini"
class="ctrl-input"
type="number"
@change="(page) => handlePage('input', page)"
/>
<span>/ {{ page.pages }}页</span>
</span>
<div class="select-btn">
<i
:class="['el-icon-arrow-right', {'ban-btn':banNext}]"
@click="handlePage('next')"
/>
</div>
</div>
<div class="scale-control">
<i class="el-icon-narrow-l" @click="emitControl('scaleControl', -1)" />
<span>{{ page.scale + "%" }}</span>
<i class="el-icon-amplification-m" @click="emitControl('scaleControl', 1)" />
<i class="el-icon-adapt" @click="emitControl('scaleControl', 0)" />
</div>
</div>
</template>
<script>
export default {
name: 'FooterAi',
filters: {
modefilter(val) {
switch (val) {
case 0:
return '两栏'
case 1:
return '正文页'
case 2:
return '答案页'
default:
return '两栏'
}
}
},
props: {
mode: {
type: Number,
default: 0
},
page: {
type: Object,
default: () => null
}
},
data() {
return {
currentPage: this.page.curPage
}
},
computed: {
banPrev() {
return this.currentPage === 1
},
banNext() {
return this.currentPage === this.page.pages
}
},
methods: {
/**
* 事件分发
* @param {*} type
* @param {*} val
*/
emitControl(type, val) {
this.$emit(type, val)
},
/**
* 页码控制
* @param {*} type
* @param {*} page
*/
handlePage(type, page) {
if (type === 'input') { // 输入框改变页码
if (page > this.page.pages) {
this.$message.warning('超过最大页数!');
this.currentPage = 1;
return
} else if (page <= 0) {
this.currentPage = 1;
return
}
this.currentPage = page
} else if (type === 'prev') { // 上一页
if (this.banPrev) return
if (this.currentPage > 1) this.currentPage--
} else if (type === 'next') { // 下一页
if (this.banNext) return
this.currentPage++
}
this.emitControl('pageChange', {
type,
page: Number(this.currentPage)
})
}
}
}
</script>
<style lang="scss" scoped>
.footer-ai{
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 36px;
padding: 2px 20px;
display: flex;
flex-flow: row nowrap;
align-items: center;
justify-content: space-between;
background-color: #F5F5F5;
.page-select{
display: flex;
flex-flow: row nowrap;
align-items: center;
height: 28px;
line-height: 28px;
cursor: pointer;
.select-btn{
margin: 0 10px;
&:hover{
color: #FFAE0D;
}
}
.select-btn-disable{
background-color: #edf1f2;
}
::v-deep .el-input{
width: 40px;
margin-right: 10px;
.el-input__inner{
padding: 0 10px;
color: #FFAE0D;
text-align: center;
}
}
::v-deep input::-webkit-outer-spin-button,
::v-deep input::-webkit-inner-spin-button {
-webkit-appearance: none !important;
}
}
.scale-control{
display: flex;
align-items: center;
flex-flow: row nowrap;
i {
font-size: 24px;
cursor: pointer;
color: #FFAE0D;
margin: 0 5px;
width: 24px;
height: 24px;
background-size: 20px;
&:last-child{
background-position: center;
}
}
}
}
</style>
2.3、读取pdf文件
其实我们发现一进来其实默认读取第一页的数据, renderPage(1),想要切换其他页,就传入renderPage(index),注意哦,索引是从1开始的, 读取文件将文件地址传入loadFile
loadFile(url) {
PDFJS.getDocument({
url,
cMapPacked: true
}).promise.then((pdf) => {
this.pdfDoc = pdf
this.pageControl.pages = this.pdfDoc.numPages
this.$nextTick(() => {
this.pageControl.curPage = 1
this.renderPage(this.pageControl.curPage)
})
}, (err) => {
if (err.name === 'MissingPDFException') {
this.$message.warn('无效的PDF链接')
}
})
}
2.4、pdf文件的放大缩小
上面几点把预览,分页和拖动讲了,现在开始放大缩小了
放大缩小主要有两种:
1、鼠标滑轮控制
@mousewheel="(e) => scaleDom(e, 'wheel')"
2、自定义按钮控制
this.scaleDom(val, 'click')
// 放大
this.scaleDom(1, 'click')
// 缩小
this.scaleDom(-1, 'click')
通用方法:我设置最大放大2倍,最小缩放0.2倍,当然可以改,根据你们自己想要的来,当修改缩放比例后,再调用renderPage方法重新绘制
/**
* 鼠标滑轮事件
* @param {*} e
*/
scaleDom(e, type = 'wheel') {
let scaleReal = this.pageControl.beforeScale
const size = type === 'wheel' ? e.wheelDelta / 1200 : parseFloat(e / 10)
scaleReal += size;
if (scaleReal >= 0.18 && scaleReal <= 2) { // 不能直接取 0.2, 因为浏览器不同,可能每次size都不能刚好为 0.1
this.pageControl.beforeScale = Number(scaleReal.toFixed(2))
this.pageControl.scale = Number((Number(scaleReal.toFixed(2)) * 100).toFixed(0))
this.renderPage(this.pageControl.curPage)
}
}
2.5、pdf的截图
关于这里,我也在弄,目前还没出来,弄出来后会更新的
本文含有隐藏内容,请 开通VIP 后查看