1. 实现背景
产品说要实现一个功能,移动端h5的 pdf的预览和下载。 好吧,那就预研一下开始实现吧。 知道这块之前有实现过,但是ios是预览效果, 安卓手机就直接下载了。 所以就直接废弃没有实现, 改为预览图片, 然后可以自行保存图片, 其实这样子实现的感觉也还可以的,主要转换给了后端。 这次要改为pdf的预览和下载,那么工作量就给到前端了。 在实现过程中,也是遇到了一些坑以及奇怪的问题, 这边进行记录总结一下。
2. 实现过程
pdf 预览:
2.1 pdfjs实现
首先看网上实现说是pdfjs实现效果很好,所以就按照步骤来进行实现了。
贴一下pdfjs的地址: github.com/mozilla/pdf…
然后将pdfjs的内容进行clone下来, 然后将下面的web 与 build 文件夹 移入到自己项目的public文件夹下(可直接引用),ps: 这里好像也有坑,这里复制的文件夹是后端给我的,他写的时候好像说是某些时候也会预览不了,所以他改了源码。 说是他刚开始下载的是4.x版本, 后面改为3.x版本就可以的。
然后 当页面预览时,直接引入就可以了。
<div class="pdf-preview-container" v-if="show">
<iframe :src="curpdfUrl" class="pdf-iframe">
</iframe>
</div>
this.curpdfUrl = '/PDFView/web/viewer.html?file=' + encodeURIComponent(this.pdfUrl.split(",")[i])
这里的pdf的路径 需要encodeURIComponent 转码一下。
这个的实现效果很好, 就是类似于pc端那种pdf预览,
甚至可以自行在pdf上编辑标注等,我觉得是很完美了。
但是当把代码构建上去的时候, 测试反馈页面初始进去会很慢。 嗯? 开始进行反思, 其实我自己加载的时候也还好, 可能是是因为她开了停用缓存吧, 每次加载资源就是会这么慢的。 当我也停用缓存的时候,我发现是真的有点慢。 这时候开始有点不清楚了,因为确实引入了很多文件到项目中, 这两个文件夹的内容, 打包了一下看有370个文件好像。 我也确实不太喜欢这种方式, 把文件夹加入到自己项目中,会使项目变得越来越臃肿。
在初始引入的时候, 就有想过引入包的形式, 好吧,看了一圈,pdfjs好像不支持包的引入,怎一个难用了得。
2.2 pdfjs-dist实现
pdfjs实现不好,那就换一种方式吧。 看pdfjs-dist实现的也很多, 主要的原理就是将pdf转换为canvas进行绘制。
在整理过程中 之前写的代码 也没完全找到了。没有我的提交记录。 那就大致写一下吧。
<div>
<canvas v-for="page in pages" :id="'the-canvas'+page" :key="page"></canvas>
</div>
import PDFJS from 'pdfjs-dist';
import workerSrc from 'pdfjs-dist/build/pdf.worker.entry';
PDFJS.workerSrc = workerSrc;
data() {
return {
pdfDoc: null,
pages: 0,
pdfUrl:'',
src:'',
loadding:true,
file:true,
isDestory:true
}
},
_renderPage (num) {
// getPage 处理每个页面
// 返回单页内容实例(页面引索) pdf.getPage(index)
this.pdfDoc.getPage(num).then((page) => {
// canvas 绘制 PDF
let canvas = document.getElementById('the-canvas' + num)
let ctx = canvas.getContext('2d');
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
let dpr = window.devicePixelRatio || 1
let bsr = ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1
let ratio = dpr / bsr
// 返回页面内容(比例) page.getViewport({scale:2.0})语法改这么写
let viewport = page.getViewport({scale:screen.availWidth / page.getViewport({scale:1.0}).width});//这是让pdf文件的大小等于视口的大小
canvas.width = viewport.width * ratio
canvas.height = viewport.height * ratio//这里会进行压缩,解决模糊问题
canvas.style.width = viewport.width + 'px'
canvas.style.height = viewport.height + 'px'
let renderContext = {
canvasContext: ctx,
viewport: viewport,
transform: [ratio, 0, 0, ratio, 0, 0]//这里会进行放大,解决模糊问题
}
const that = this
page.render(renderContext).promise.then(function(){
// 防止 在 PDF未渲染完某页 就返回时 会报错,导致下一次打开PDF 不会渲染出来
if(that.isDestory){
console.log('xuanran')
// page.getTextContent()
if (that.pages > num) {
that._renderPage(num + 1)
}
}
});
},
_loadFile (url) {
// 获取整个 文档
PDFJS.getDocument({
url,
// cMapUrl: 'https://unpkg.zhimg.com/pdfjs-dist@1.9.426/cmaps/',//这里同样要引入字体解决水印问题,需自己提供
// 注意:如果PDF的水印是收费字体,则需要 通过cMapUrl引入对应的字体,如果是免费字体,则不需要引入字体 水印会直接渲染出来
cMapPacked: true
}).promise.then((pdf) => {
this.pdfDoc = pdf
this.pages = this.pdfDoc.numPages
this.loadding = false
const that = this
if(that.isDestory){
this.$nextTick(() => {
this._renderPage(1)
})
}
},(err) => {
if(err.name == 'MissingPDFException'){
this.$toast('无效的PDF链接')
}
// reject(err);
})
}
},
mounted() {
this._loadFile('http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf');
},
版本: "pdfjs-dist": "^2.5.207", npm i pdfjs-dist@2.2.228 可自行进行安装。
但是 实现的效果一般,绘制出来的图片有点变形了。 也有可能是我参数没有调好。
2.3 pdfh5实现
继续查找, 这个pdfh5实现的使用也比较多,那也尝试一下吧。
贴一下地址:github.com/gjTool/pdfh…
<div class="pdf-preview-container" v-if="show">
<div id="demo"></div>
</div>
import Pdfh5 from "pdfh5";
import "pdfh5/css/pdfh5.css";
/**
* pdf 预览
*/
previewPdf(i) {
// this.curpdfUrl = '/PDFView/web/viewer.html?file=' + encodeURIComponent(this.pdfUrl.split(",")[i])
this.curpdfUrl = this.pdfUrl.split(",")[i]
this.show = true
//实例化
setTimeout(() => {
this.pdfh5 = new Pdfh5("#demo", {
pdfurl: this.curpdfUrl,
// cMapUrl:"https://unpkg.com/pdfjs-dist@3.8.162/cmaps/",
// responseType: "blob" // blob arraybuffer
});
})
//监听完成事件
// this.pdfh5.on("complete", function (status, msg, time) {
// console.log("状态:" + status + ",信息:" + msg + ",耗时:" + time + "毫秒,总页数:")
// //禁止手势缩放
// this.pdfh5.zoomEnable(false);
// })
},
尝试了一下 使用效果还是不错的, 文档也没有变形,类似于文档的形式。 可以放大缩小,也有自带的回到顶部按钮。
给产品看了一下, 产品说 这种效果是可以的。很好,完成。
pdf 下载:
使用的下载方式, 是创建一个a链接的方式。 但是这种方式苹果手机可以正常下载,但是安卓的手机,就是提示要打开浏览器,而且打开了浏览器也没有正确的进行下载。
安卓基于微信系统下是没法直接下载的。
那就区分一下安卓与ios系统吧, 实现方式如下:
downloadPdf(i) {
const userAgent = navigator.userAgent;
const isIOS = /iP(hone|od|ad)/.test(userAgent);
// 安卓和ios 是走不同的下载方式。ios可直接下载, 安卓需从浏览器打开下载
if (isIOS) {
// ios 系统
console.log("用户设备是iOS");
fetch(this.pdfUrl.split(",")[i])
.then(response => response.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = `报告${i+1}.pdf`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
})
.catch(error => console.error('Error downloading the PDF:', error));
} else {
// 安卓系统
console.log("用户设备不是iOS");
window.open(this.pdfUrl.split(",")[i])
}
},
这时候,安卓手机就是跳转到外部浏览器进行下载, ios系统可自行下载或转发。
部分pdf打不开问题?
安卓手机反馈部分pdf会提示: 可在浏览器打开此网页下载文件。 但没有直接跳转浏览器。 原因是: 有的pdf上传的不规范, content-type = application/pdf, content-type有的不规范。这个就需要花时间调试了,就没管了。
3. 实现感想
其实整个过程,实现起来还是比较简单的。但是感觉自己也是需要不断的学习与进步, 在查找资料过程中,需要辨别与实现一下最优方式。
学无止境, 继续加油呀~
还有就是 就算是困困的时候,居然还是写出来了,感觉和梦游一样。