工作学习总结-pdf.js的踩坑和运用

周末了,终于有这么一整段的时间去总结整理了,这个月很忙,需求很多,年龄不小了,生活中个人的事情也不少。时间是最公平的,因为每个人每天都是过这么长时间,好好珍惜每一分。好了,不感慨了。进入正题吧。
先说一下项目结构:angular5.0 + ionic3.0 这个月做了一个pdf展示的需求,后台小伙伴传过来一个pdf格式的url,
这个url长这样:

url = ”https://sg.ibs.baidu.com.cn/download/fla-ybkj-dmz-dev-pri/24a1bed5f3fc46d582d6ff7747445b14?attname=BS012LYT.pdf"

其实就是一个pdf格式的文件的url地址,然后前端展示一下。原本以为实现会很简单,没想坑是一个接一个,下面来一一说一下吧。

1.先把后台传过来的url做一下转化,在angular中的转化如下:

 html的代码
    
        
    
    ts中的代码
    this.url = this.sanitizer.bypassSecurityTrustResourceUrl("assets/viewer/web/viewer.html?file=" + encodeURIComponent(res.data.url));

只有转化之后的url加载到iframe中才有效

2.这个pdf.js是一个泛称,装上之后,会有这么多文件,如图:
工作学习总结-pdf.js的踩坑和运用_第1张图片
html页面中写法如下:


    

其中
scrolling属性规定是否在 iframe 中显示滚动条。有三个值 yes 、no、 auto
seamless属性规定 看上去像是包含文档的一部分。
scrollEvent是监听ion-content 内容的滚动事件。然后就开始在scrollEvent里面增加滚动后的触发事件了,代码如下:

scrollEvent(event) {
    this.show = true;
    let het1 = document.body.clientHeight;
    let het2 = document.getElementById("deatilShow").clientHeight; 
    this.ngzone.run(() => {
      let top = event.scrollTop; 
      if (het2 <= het1 + top) {
        this.buttonShow = false;
      }
    })
  }

上面代码中的this.ngzone.run()的用法请见我的另外一篇文章工作学习总结–angular中的NgZone的简单使用

当我滚动页面的时候,scrollEvent事件却没有触发,因为我滚动的是iframe页面,也就是嵌入的另外一个页面,所以不会触发scrollEvent事件 。所以我找了一个折中的方案,给iframe加了一个margin-bottom,当iframe滚动到底部的时候,再往上滑一点,就会触发这个事件了,也就是ion-content这个页面滚动了,但是效果不太好,在ios和安卓上体验都不是很好,所以最后也没有采用这种方法。那怎么办呢?
经过自己的搜索+思索和其他小伙伴的帮助,最后还是以一种笨方法解决了。

3.下面描述我探索的过程(探索方法一):
我首先获取iframe这个元素,监听它的滚动事件,获取到的iframe是一个html,如图:
工作学习总结-pdf.js的踩坑和运用_第2张图片

所以给它加滚动事件无效,然后又百度方法,还是没找到有效的方法,最后谷歌了一下,找到了一个方法,就是监听irame中的id为viewerContainer的元素的滚动事件才行,这个id为viewerContainer的元素其实就是viewer.html中的一个元素,所有pdf文件页面都加载到它里面,它在viewer.html中的代码如下:

然后就去获取这个元素

        setTimeout(() => {
                if(this.elementRef.nativeElement.querySelector('.scroll-content #deatilShow') && this.elementRef.nativeElement.querySelector('.scroll-content #deatilShow').contentDocument && this.elementRef.nativeElement.querySelector('.scroll-content #deatilShow').contentDocument.getElementById("viewerContainer")) {
                clearInterval(this.timer)
                viewerContainer = this.elementRef.nativeElement.querySelector('.scroll-content #deatilShow').contentDocument.getElementById("viewerContainer");
                viewerContainer.addEventListener('scroll',(event) => {
                  let scrollTop = event.target.scrollTop ;
                  let clientHeight = event.target.clientHeight ;
                  let scrollHeight = event.target.scrollHeight ;
                  if(scrollTop+clientHeight == scrollHeight){
                      // 添加滚到底部要处理的事件
                  }
                });
              }
            },2000)

为什么加个定时器呢?是因为网速不好的时候,pdf加载很慢,有可能页面还没加载完全,可能这个时候获取不到元素。这个时候在浏览器上跑的时候报跨域的问题,所以在谷歌浏览器设置了一下跨域。这个时候页面上事件都是可以的,都可以触发了。然后打包在ios和安卓手机上测的时候无效,一点效果没有,这个时候就纳闷了。为什么呢?手机连着电脑调试了一下,发现还是viewerContainer这个元素没有获取到。查了一下,猜测应该是pdf.js安全策略,不让外界获取viewerContainer这个元素,因为pdf文件都加载到这个元素下面,怕外界对pdf文件进行修改操作。那电脑上为什么可以呢,也许是因为给谷歌设置了跨域,可以忽略这个风险,具体原因还需后续再仔细去查一下。这个方法又被pass掉了,继续想办法吧

4.探索方法二
获取不到这个元素就没有办法监听它的滚动事件。那既然外界获取不到,我就在viewer.html中获取这个元素,并给它监听这个滚动事件。先试一下,试试又不花钱。
于是我在viewer.html中给它加了这样一段代码


然后在页面上监听postMessage发出的事件就可以了

ngOnInit() {
        window.addEventListener("message",(event)=>{
               event.data.toBottom?this.show = true:"";
               event.data.scrolled?this.button = true:"";
        })
   }

问题总算解决了,但是这也只是一个临时的方案,后期如果有什么更好的方法还要去优化的。
这个方法中有两个问题需要注意一下,一个是在viewer.html页面中用 window.parent.postMessage发出消息,另外就是在父页面进行监听的时候,用event.data的数据来区分监听是哪种消息。

5.最后说一下我对pdfjs这块的源码做了哪些改动
第一点:我注释掉了跨域这一块,在viewer.js里面(在1751行到1753行)

 try {
      var viewerOrigin = new _pdfjsLib.URL(window.location.href).origin || 'null';

      if (HOSTED_VIEWER_ORIGINS.includes(viewerOrigin)) {
        return;
      }

      var _ref8 = new _pdfjsLib.URL(file, window.location.href),
          origin = _ref8.origin,
          protocol = _ref8.protocol;

      // if (origin !== viewerOrigin && protocol !== 'blob:') {
      //   throw new Error('file origin does not match viewer\'s');
      // }
    } catch (ex) {

第二点:需求需要,需要合同文件上展示签章,也就是盖过的章,所以在pdf.work.js里面注释掉这样几行代码
大约注释了四行代码(在28684行到28688行)

 if (!Number.isInteger(data.fieldFlags) || data.fieldFlags < 0) {
      data.fieldFlags = 0;
    }

    data.readOnly = _this2.hasFieldFlag(_util.AnnotationFieldFlag.READONLY);

    // if (data.fieldType === 'Sig') {
    //   data.fieldValue = null;

    //   _this2.setFlags(_util.AnnotationFlag.HIDDEN);
    // }

    return _this2;
  }

我使用的pdf.js是2.0的版本

第三点:清除pdf缓存 为什么说清除pdf缓存呢?例如 我打开一个pdf文件,滑到第6页,然后退出这个页面,再进来还是在第6页。那有没有方法可以让文件从第1页开始加载呢。就开始了我的探索之路,其实开发经验丰富的人,找问题很快也很准,也许是因为开发的需求多了,接触的问题也多了。我先去在插件里面去找,在viewer.js里面找。缓存就是意味着历史记录,于是我先查history,我发现了有这样一行代码:

localStorage.setItem('pdfjs.history', databaseStr);

我感觉这就是我要找的,于是我就去浏览器的localstorage里面找,果然,请看图:工作学习总结-pdf.js的踩坑和运用_第3张图片
所以我在viewer.js中的第13457行把它注释掉了

_regenerator.default.mark(function _callee() {
        var databaseStr;
        return _regenerator.default.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                databaseStr = JSON.stringify(this.database);
                // localStorage.setItem('pdfjs.history', databaseStr);  

              case 2:
              case "end":
                return _context.stop();
            }
          }
        }, _callee, this);
      }));

然后就没有缓存了,问题也解决了。
还有一种笨方法就是在pdf的url后面加一个时间戳或者随机数。
以后关于pdf.js遇到的问题,到时候还会补充。

你可能感兴趣的:(angular,工作学习总结,清除pdf.js的缓存,pdf.js的跨域,pdf.js展示电子签章)