ionic3中实现浏览PDF文件

说实话这个问题还挺难得,搞了两天才出结果,身心疲惫啊。导致身心疲惫的还有另外一件事,项目组的实习生想要追求“美好”的生活,决定跑到上海去试试。看着他高兴期待兴奋的样子实在不忍泼冷水。去了一个月就回来了,没找到合适的工作不说还被对方的“技术主管”气势汹汹的一通打压嘲讽。

这让我想起来了一个不是笑话的笑话。程序猿相亲程序媛,一见面挺对眼的。聊着聊着就出事了,虽然男方已经极力避免出现编程界割席断袍的几个致命话题,例如“PHP是世界上最好的语言”之类的。可惜还是中招了。导致相亲失败的原因很可笑,是关于一个三元运算符的问题。女方觉得男的连三元运算符都掌握不好,技术肯定很差,两个人不适合,然后就没有然后了……其实吧,我觉得挺好的分了就分了呗,不然真成事了,白天干程序员,晚上还程序员,估计是要疯了的!

what???.jpg

人总是会有优越感的,健康的优越感应该是来自努力奋斗并且成功之后的充实感,而不是建立在打压,嘲笑别人。算是为了我的小兄弟鸣个不平吧。开始说重点吧,不然各位要抄起板砖拍人了。老规矩手打文章妹子镇楼,不为别的,只为活血化瘀……

工作细胞—血小板

不放性感妹子了,来个萌物。一样活血化瘀!

话说ionic在国内还真是不火啊,不过这不影响我们使用的热情。在ionic中浏览PDF是个比较难的需求,不过我们始终坚信技术上的难题从来都不会成为真正的难题。下面是我的探索之路的回溯!也是所谓的装逼时间。

其实在官方文档上已经有现成的插件可以实现阅读PDF——Document Viewer。不过这货有个致命的缺点,就是只能阅读本地PDF文件,对于网络PDF文件支持的不是很好。如果想要阅读远程PDF还必须使用FileFile 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。还好插件的说明文档给了相关提示,查阅源码的时候发现果然如此。

ng2-pdf-viewer

好吧,构造函数constructor是整个页面中最先被执行到的在这里面添加本地化方法吧。先把pdf.worker.jscopy到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.jsPDFDocumentProxy。它返回的属性有个numPages就是PDF文档的总页数,继续进行。

<!--html-->
 <pdf-viewer (after-load-complete)="afterLoad($event)"></pdf-viewer>
//ts
afterLoad(pdf: PDFDocumentProxy) {
        this.totalPage = pdf.numPages;
    }

这样我们就能得到总页数了,之后的的翻页功能我就不写出来了,对你来说应该是小case了。

最后的最后,还有一个坑,需要去填的就是。这个插件访问的远程PDF文件如果是不存在的,整个APP会崩掉。这就尴尬了!


尴尬.jpg

如何判断远程文件是不是存在,还需要一些方法。想来想去也就是用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。可恶啊,懒的再去看插件源码了。

就这吧!抽空再搞一搞吧。
(报告完毕)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容