vue整合pdfjs
浏览器是能直接接收pdf文件流的,pdf以iframe的格式嵌入到页面中。以防有需要的朋友,代码示例如下:
/**
sealPdfApi:接口,参数为data,返回值为pdf文件流;
*/
sealPdfApi(data).then(res => {
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
this.$notify.error({ title: "请使用google浏览器" });
} else {
var blob = new Blob([res], { type: "application/pdf" });
this.pdfViewer = URL.createObjectURL(blob);//创建的pdf链接
window.open(this.pdfViewer);//打开窗口,也可以页面使用iframe,src等于上面的pdf链接
}
})
使用以上方法需要特别注意的一点就是接口调用时,响应类型必须是blob类型,一般可以在项目的通用接口配置中修改。
responseType: 'blob'
虽然这样的方法能正确显示pdf,但是对于公司的盖章需求有几个问题。
- 拖拽盖章,一个pdf可能需要盖多个章
- 盖章是需要调用后端接口的,接口参数需要每个印章在pdf中的位置 针对上面的需求,我一开始的想法是将想在iframe的外面包一层div,利用印章在div中的位置去获取对应的pdf坐标位置。但是这样另一个问题很难解决,如何在盖完章之后将章保留在页面上。想出来的方法相对来说都比较麻烦,感兴趣的大佬们可以尝试研究一下。
最终决定了一个方案,整合pdfjs,讲pdf用canvas的方式在页面上显示,在canvas的外层包裹一个div,获取印章在div块中的位置,在canvas中使用 ctx.drawImage()
方法来讲印章绘制到pdf中。
以下是整合pdfjs的组件部分代码,项目地址我会放在文章的底部:
export default {
name: 'Pdf',
props: {
url: {
type: String,
default: ''
},
type: {
type: String,
default: 'canvas'
},
pdfjsDistPath: {
type: String,
default: '.'
}
},
data() {
return {
pdfViewer: null,
pdfLinkService: null,
currentScale: '1.0',//缩放比例
loadingTask: null
}
},
methods: {
onPagesInit({ source }) {
source.currentScaleValue = this.currentScale
},
async pdfLibInit() {
let pdfjsLib = window.pdfjsLib;
let pdfjsViewer = window.pdfjsViewer
if (!pdfjsLib || !pdfjsViewer) {
try {
await getPdfjsDist(this.pdfjsDistPath)
this.CMAP_URL = `${this.pdfjsDistPath}/pdf/cmaps/`;
// console.log( this.CMAP_URL)
pdfjsLib = window.pdfjsLib;
pdfjsLib.GlobalWorkerOptions.workerSrc = `${this.pdfjsDistPath}/pdf/build/pdf.worker.js`
pdfjsViewer = window.pdfjsViewer
} catch (error) {
// console.log(error)
// pdfjs文件获取失败
return
}
}
const container = this.$refs.container
const eventBus = new pdfjsViewer.EventBus();
// (Optionally) enable hyperlinks within PDF files.
const pdfLinkService = new pdfjsViewer.PDFLinkService({
eventBus: eventBus,
});
this.pdfLinkService = pdfLinkService
const pdfViewer = new pdfjsViewer.PDFViewer({
container: container,
eventBus: eventBus,
linkService: pdfLinkService,
renderer: this.type,
textLayerMode: 0,
downloadManager: new pdfjsViewer.DownloadManager({}),
enableWebGL: true
});
this.pdfViewer = pdfViewer
pdfLinkService.setViewer(pdfViewer);
eventBus.on("pagesinit", this.onPagesInit);
},
renderPdf() {
if (!this.url) { return }
// Loading document.
if (this.pdfViewer === null || this.pdfLinkService === null) {
return
}
if (this.loadingTask !== null) {
this.loadingTask.destroy()
this.loadingTask = null
}
this.loadingTask = window.pdfjsLib.getDocument({
cMapUrl: this.CMAP_URL,
cMapPacked: true,
url: this.url,
});
return this.loadingTask.promise.then((pdfDocument) => {
if (pdfDocument.loadingTask.destroyed || !this.url) { return }
this.pdfViewer.setDocument(pdfDocument)
this.pdfLinkService.setDocument(pdfDocument, null);
this.loadingTask = null
}).catch(error => {
console.log(error)
});
}
},
mounted() {
this.pdfLibInit().then(() => {
this.renderPdf()
})
},
watch: {
url() {
// 如果存在pdfViewer则取消渲染
if (this.pdfViewer) {
this.pdfViewer._cancelRendering()
}
this.renderPdf()
}
},
render() {
return (
<div class="pdf-view">
<div id="viewerContainer" ref="container">
<div id="viewer" class="pdfViewer"/>
</div>
</div>
)
}
}
以上代码会将pdf以canvas形式渲染到页面上。 组件调用代码如下:
<pdf :url="pdfUrl" :type="'canvas'" :pdfjsDistPath="'/static'" ref="pdf"/>
pdfUrl是pdf文件地址,如果后端返回的是文件流类型,就使用上述步骤整合出来的地址。pdfjsDistPath是pdfjs文件放置地址,这里我直接放到了vue项目的 static 中。(附带:该目录下的文件是不会被wabpack处理的,它们会被直接复制到最终的打包目录下面(默认是 dist/static
),且必须使用绝对路径来引用这些文件。任何放在 static 中的文件需要以绝对路径的形式引用:/static/[filename]
。)
印章拖拽
实现拖拽的要素就是让图片脱离文档流。我将图片跟pdf组件放在同一个div下,给div的position设置relative,再给印章图片的position设置为absolute。
接下来要实现的是鼠标在移入pdf中时,印章出现,并且位置随着鼠标移动。给外层div加一个mouseMove事件。
move(e) {
if (this.signUrl !== "") {
var pdfContainer = document.getElementById("pdf-container");
var scrollY = pdfContainer.scrollTop;
var moveX = e.clientX - pdfContainer.offsetLeft - 200;
var moveY = e.clientY - pdfContainer.offsetTop + scrollY;
var SignImg = document.getElementById("pdf-sign-img");
if (moveX < 0) {
SignImg.style.left = 0 + "px";
} else if (
moveX >
document
.getElementsByClassName("canvasWrapper")[0]
.getElementsByTagName("canvas")[0].offsetWidth -
SignImg.offsetWidth +
11
) {
SignImg.style.left =
document
.getElementsByClassName("canvasWrapper")[0]
.getElementsByTagName("canvas")[0].offsetWidth -
SignImg.offsetWidth +
11 +
"px";
} else {
SignImg.style.left = moveX + "px";
}
SignImg.style.top = moveY + "px";
}
},
以上代码中需要注意的是处理鼠标移动的边界问题,确保印章是在pdf所在的第一层div中移动。
盖章
盖章的操作主要在于找到正确的坐标点,然后调用canvas的drawImage
方法。
这里有一个问题,就是渲染出来的页面,每一页对应一个canvas,首先我们需要计算出盖章位置对应的是页数,然后用查找到对应的dom元素下的canvas。
查找页数的方法就是目前图章所在的位置除以每一页的高度取整。
var pageIndex = 0;
var myCanvas = document
.getElementsByClassName("canvasWrapper")[0]
.getElementsByTagName("canvas")[0];
if (CanvasTop > myCanvas.offsetHeight) {
//获取当前印章所在页数
pageIndex = Math.floor(CanvasTop / myCanvas.offsetHeight);
myCanvas = document
.getElementsByClassName("canvasWrapper")[pageIndex].getElementsByTagName("canvas")[0];
}
找到对应的canvas之后,接下来需要计算每个印章在canvas中对应的坐标,x坐标比较容易计算,不管是在每一页都是不变的,
e.clientX - odiv.offsetLeft;
y坐标需要用图章的纵坐标减去每一页的高度乘页数。
ctx.drawImage(img,CanvasLeft - 11,CanvasTop - myCanvas.offsetHeight * pageIndex);
这样的话我们就能将图片画在对应的canvas上面了。 后端需要盖章的坐标时也可以在盖章之后传递,需要注意的是,可能后端的pdf大小跟前端绘制的pdf大小不一致,这个时候就需要比例换算了。具体的话视情况而定,本文主要是提供一个前端盖章的思路。
撤回操作实现
项目需求中,还有一个撤回的操作。这里因为没有比较好的方案,我直接在每次盖章完成之后,使用getImageData方法,将盖章那一页的canvas像素数据存储到数组中。
if (this.orignCanvas.length > 0) {
var backCanvasInfo = this.orignCanvas[this.orignCanvas.length - 1];
var canvas = document.getElementsByClassName("canvasWrapper")[backCanvasInfo.pageIndex].getElementsByTagName("canvas")[0];
var ctx = canvas.getContext("2d");
ctx.putImageData(backCanvasInfo.canvansImg, 0, 0);
this.orignCanvas.pop();
this.sealInfo.pop();
} else {
this.$notify.error({ title: "撤回到最后一步了" })
}
},
demo代码已经发布到我的github上,有兴趣的童靴可以看一下,还有许多可以优化的地方跟一些问题,比如浏览器比例或者是电脑显示比例不是百分之百的话,会出现盖章位置错乱的情况,欢迎大佬指点!