说实话这个问题还挺难得,搞了两天才出结果,身心疲惫啊。导致身心疲惫的还有另外一件事,项目组的实习生想要追求“美好”的生活,决定跑到上海去试试。看着他高兴期待兴奋的样子实在不忍泼冷水。去了一个月就回来了,没找到合适的工作不说还被对方的“技术主管”气势汹汹的一通打压嘲讽。
这让我想起来了一个不是笑话的笑话。程序猿相亲程序媛,一见面挺对眼的。聊着聊着就出事了,虽然男方已经极力避免出现编程界割席断袍的几个致命话题,例如“PHP是世界上最好的语言”之类的。可惜还是中招了。导致相亲失败的原因很可笑,是关于一个三元运算符的问题。女方觉得男的连三元运算符都掌握不好,技术肯定很差,两个人不适合,然后就没有然后了……其实吧,我觉得挺好的分了就分了呗,不然真成事了,白天干程序员,晚上还干程序员,估计是要疯了的!
人总是会有优越感的,健康的优越感应该是来自努力奋斗并且成功之后的充实感,而不是建立在打压,嘲笑别人。算是为了我的小兄弟鸣个不平吧。开始说重点吧,不然各位要抄起板砖拍人了。老规矩手打文章妹子镇楼,不为别的,只为活血化瘀……
不放性感妹子了,来个萌物。一样活血化瘀!
话说ionic在国内还真是不火啊,不过这不影响我们使用的热情。在ionic中浏览PDF是个比较难的需求,不过我们始终坚信技术上的难题从来都不会成为真正的难题。下面是我的探索之路的回溯!也是所谓的装逼时间。
其实在官方文档上已经有现成的插件可以实现阅读PDF——Document Viewer。不过这货有个致命的缺点,就是只能阅读本地PDF文件,对于网络PDF文件支持的不是很好。如果想要阅读远程PDF还必须使用File和File Transfer(或者File Opener)这两个插件。前者操作本地文件,后者用于把远程文件下载到本地。这种方案在理论上可以很完美的实现需求。
各位……请注意!是理论上,因为远程下载文件到本地这个操作在实际APP运行环境中,几乎是不可能的。为什么?因为各个手机厂商会在此阶段接管下载文件的进程。反应到现实情况就是当你点击查看PDF的时候,手机系统会弹出对话框让你选择“始终用此方式”或“仅此一次”来打开,备选的打开方式中都是各自平台的应用市场。就算手机上有下载工具类的功能,但下载路径就不是我们可以掌控的了。这也是为什么很多APP不能实现“热更新”的原因。我们的业务流程直接被打断夭折掉。放弃吧,寻找别的方法吧。
想起来以前写管理后台的时候用过的一个叫pdf.js
的插件挺好用的,可以想个办法集成到ionic中来。知道你们最想看的是代码,这样可以直接copy到你们自己的项目直接用,我并不是反对“拿来主义”,做项目么就是要第一时间满足客户需求为首位,不过在闲暇时间研究下还是有很多乐趣的。下面就放出代码,不过事先声明,这个是我结合国外的大神的一篇文章的来的,不过我发现并不能很好的实现功能。所以这是被我摒弃的方案,具体原因会在后面说明。
祭出大神的文章Using PDF.JS with Ionic 3.x,可以参考下。打开速度有点慢,耐心等待。下面是我的方法。
第一步,在项目目录中安装pdf.js组件
npm install ‑‑save pdfjs‑dist@2.0.489 //最新版本可能会报错
npm install ‑‑save‑dev @types/pdfjs‑dist
第二步,copy你的PDF文件到项目\src\assets
中
第三步,在home.html
中添加以下代码
<ion-content padding>
<canvas #viewerContainer></canvas>
</ion-content>
第四步,在home.ts
中添加以下代码,比较简单的实现核心功能代码,翻页缩放什么的就不放了,不然太长。
import * as pdfjs from "pdfjs-dist/webpack.js";
import { PDFDocumentProxy, PDFPageProxy, PDFPageViewport, PDFRenderTask } from 'pdfjs-dist';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
scale = 1;
pdfDoc = null;
canvas = null;
curPage: number = 1;
maxPage: number = 0;
//上面是添加的代码
@ViewChild('viewerContainer') canvasRef: ElementRef;
constructor(public navCtrl: NavController) { }
ionViewDidLoad() {
this.canvas = this.canvasRef.nativeElement;
pdfjs.getDocument('https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.pdf').then(pdf => {
this.curPage = 1;
this.pdfDoc = pdf;
this.maxPage = pdf.numPages;
this.viewPage();
});
}
viewPage() {
this.pdfDoc.getPage(this.curPage).then(page => {
var viewport = page.getViewport(this.scale);
var context = this.canvas.getContext('2d');
this.canvas.height = viewport.height;
this.canvas.width = viewport.width;
var renderContext = {
canvasContext: context,
viewport: viewport
};
page.render(renderContext);
});
}
}
这种方法可以实现远程PDF文件的访问。但实现的结果是整个PDF文件在页面中渲染出来不会自适应页面大小,要想实现需要查阅pdf.js
的接口文档。吐槽下,就和“在技术人员眼里所有人都应该懂技术和在技术大神眼里所有人都应该与自己水平差不多”这种操蛋逻辑一样。pdf.js
的官方文旦太操蛋了,需要去查看源码才能了解到他的具体接口都有什么。
虽然最终实现的代码可能很少,但是这个查阅量对于没有阅读外文文档能力和阅读源码能力的人来说无意是噩梦,看一眼的勇气都没有直接放弃。这就是所谓的操作颗粒度太细了。我们需要的是给我们一堆乐高积木让我们拼城堡而不是给我们一堆沙子让我们去堆城堡。
看了一点点官方的文档,大致明白了些,毕竟不是直接调用JS来实现,在外层还嵌套了ionic。这个实现起来就需要绕个小圈圈。正在看操蛋源码的时候,突然脑海中一闪而过在早期我放弃掉的一个方案。这个方案因为没能提供“总页数”的获取方式,无法实现一些功能被我放弃了,当我意识到这个被放弃的插件也是基于pdf.js
开发的时候,天!晴!了!我在官方文档中找到了这个突破点。一切都迎刃而解了。看了看表,还有点时间才下班。争取在下班之前搞定这个需求,我们的口号是:不加班!
ng2-pdf-viewer还是比较给力的,它提供了一些接口能让我们在ionic中方便的浏览PDF,而且预先解决了一些底层的问题,让我们无须考虑这些无关项目的问题。以下在是我的最终方案,代码完全可以用。跑的起来而且没毛病,可以直接拷贝拿走的。需要事先说明是,我并没有按照官方示例的方式去做,有些安装顺序和代码可能与官方提供的有些差异。
重点:这个插件不支持ionic的 懒加载 模式,会出错!整个APP崩掉!!!
第一步,安装ng2-pdf-viewer
npm install ng2-pdf-viewer --save
第二步,在项目中新建页面
ionic g page pdf-viewerPage
第三步,在app.module.ts
添加代码,只贴出添加的代码
import {PdfViewerModule} from 'ng2-pdf-viewer';
import {PdfViewerPage} from "../pages/pdf-viewer/pdf-viewer";
@NgModule({
declarations: [
PdfViewerPage
],
imports: [
PdfViewerModule,
],
entryComponents: [
PdfViewerPage
],
})
第四步,实现方式是点击触发模态框,让PDF文件在模态框中渲染。先在需要响应点击事件的页面写代码
//html文件
<ion-list class="topList">
<ion-list-header>
<div class="headerBock"></div>
某某列表
</ion-list-header>
<ion-item *ngFor="let n of news;" (click)="openPDF()">
<ion-thumbnail item-start>
<img src="{{n.thumbnail}}" class="smallThumbnail"/>
</ion-thumbnail>
<h2>{{n.listTitle}}</h2>
<p text-right>{{n.created}}</p>
</ion-item>
</ion-list>
//ts文件
import {PdfViewerPage} from "../pdf-viewer/pdf-viewer";
@Component({
selector: 'xxxx-home',
templateUrl: 'xxxx.html'
})
export class XxxxPage {
URL= "https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.pdf";
constructor(public modalCtrl: ModalController,) {}
openPDF(){
let modal: Modal = this.modalCtrl.create(PdfViewerPage, {
displayData: {
pdfSource: {
url: this.URL
}
},
});
modal.present();
}
}
第五步,写响应模态框请求的页面pdf-viewer.html
<ion-content padding >
<pdf-viewer [src]="displayData.pdfSource"
[show-all]="true"
[original-size]="false"
[zoom]=1
[render-text]="false"
[autoresize]="true"
style="display: block" >
</pdf-viewer>
</ion-content>
第六步,pdf-viewer.ts
displayData: any = {};
ionViewDidLoad() {
this.displayData = this.navParams.get('displayData');
}
没了,显示结果很是不错。完美自适应屏幕大小,也能远程访问PDF文件。需要注意的是此插件会调用
pdf.worker.js
这个文件,我很纳闷安装这个插件的时候,会自动安装pdfjs‑dist
插件,他却通过CDN远程调用pdf.worker.js
。还好插件的说明文档给了相关提示,查阅源码的时候发现果然如此。
好吧,构造函数constructor
是整个页面中最先被执行到的在这里面添加本地化方法吧。先把pdf.worker.js
copy到assets/public/
下面。然后在pdf-viewer.ts
写上。
constructor(public navCtrl: NavController, public navParams: NavParams,) {
window.prototype.pdfWorkerSrc = 'assets/public/pdf.worker.js';
}
发现cmaps也能本地化,好吧一起来吧。在node_modules
找到pdfjs-dist/cmaps
,把整个cmaps文件夹拷贝到assets
目录下。然后在pdf-viewer.html
的<pdf-viewer>
添加自定义属性[c-maps-url]="'assets/cmaps/'"
<pdf-viewer [src]="displayData.pdfSource"
.
.
[c-maps-url]="'assets/cmaps/'"
.
.
></pdf-viewer>
这时候出现了一个十分十分操蛋的问题,整个页面如果没有render结束,就去关闭模态框会报错。告诉DIV找不到属性之类的。奶奶个腿啊,如果一个PDF的页数比较多,这可能要了老命了。为了不加班加速干起来。能精简的就精简吧!不计后果了。
解决方案就是不全部加载,每次只render一页。然后添加控制按钮或者是手势响应事件,来控制翻页。这种情况必须知道页面的总数,没关系pdf.js
的文档中已经告诉了我们方法。
ng2-pdf-viewer
中提供的事件(after-load-complete)
。会在文档加载完成之后执行回调函数,回调函数的参数类型就是来自于pdf.js
的PDFDocumentProxy
。它返回的属性有个numPages
就是PDF文档的总页数,继续进行。
<!--html-->
<pdf-viewer (after-load-complete)="afterLoad($event)"></pdf-viewer>
//ts
afterLoad(pdf: PDFDocumentProxy) {
this.totalPage = pdf.numPages;
}
这样我们就能得到总页数了,之后的的翻页功能我就不写出来了,对你来说应该是小case了。
最后的最后,还有一个坑,需要去填的就是。这个插件访问的远程PDF文件如果是不存在的,整个APP会崩掉。这就尴尬了!
如何判断远程文件是不是存在,还需要一些方法。想来想去也就是用http
组件的get
方法了。不过这个玩意一般都是用来获取json的,能用到PDF文件么?两者就后缀名不一样,都是文件啊。还是试验下……
this.http.get('https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.pdf')
.subscribe(
data => {console.log(data)},
error => {console.log(error)}
);
全部都是走的error
这条路!还让不让人活啦!
等等!我好像遗漏了什么东西,仔细观察下。发现了不得了的东西。error
对象里面有一个只读属性status
它显示的是200
,这个数很熟悉。我把远程PDF的名字后面随便加了一个字母,status
变成了404
。我擦我草我勒个去。不会吧!可以这样么?那么我可以利用get
远程PDF文件必然走错误路径,在根据status
的数值来判断远程是否存在。有点奇葩了吧!
好吧 不管了为了早下班。
let URL = 'https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.pdf';
this.http.get(URL).subscribe(data => {},
error2 => {
if (error2.status == 200) {
let modal: Modal = this.modalCtrl.create(PdfViewerPage, {
displayData: {
pdfSource: {
url: URL
}
}});
modal.present();
} else {
//弹出alert告诉用户远程PDF不存在。
}
});
总觉得有什么地方不对劲啊,算了就这吧。用户使用体验还可以。目前还没出什么乱子,暂时这样吧。很多项目都是BUG驱动,没办法啊。
远程访问PDF有个瓶颈就是如果远程文件过大,打开的时候速度会很慢。这个没办法啊!可以利用(on-progress)
加个进度条提示加载进度。可惜这个事件的回调函数返回的对象中有两个属性,一个是当前加载文件大小这个很正常,另一个总大小显示是undefined
。可恶啊,懒的再去看插件源码了。
就这吧!抽空再搞一搞吧。
(报告完毕)