作者:子木 政采云前端团队
转发链接:https://mp.weixin.qq.com/s/Wx_gJLrZftJ_dm2phoUf8g
引言
最近接到产品需求,用户需要在我们可站点上在线查看 PDF 文件,并且查看时,用户可以对 PDF 文件的进行旋转、缩放、跳转到指定页码等操作。
这个太简单了,随便找找就一堆轮子。
目前常见的在线 PDF 查看方案:
- 使用 iframe、embed、object 标签直接加载
采用此方案,只需要直接将 PDF 的在线地址设置为标签的 src 属性
- 使用第三方库 PDF.js 加载
这个方案麻烦一点,我们需要在项目中引入 PDF.js 这个库,然后再使用 iframe 来加载指定的 HTML 文件(下文代码中的 viewer.html ),并且将需要访问的 PDF 的在线地址作为参数传递进去。大概就像下面一样:
showPdf (selector, options) { const { width, height, fileUrl } = options; this.pdfFrame = document.createElement('iframe'); this.pdfFrame.width = width; this.pdfFrame.height = height; this.pdfFrame.src = `./assets/web/viewer.html?file=${encodeURIComponent(fileUrl)}`; document.getElementById(selector).append(this.pdfFrame);}
这里可能会遇到跨域的问题,不过不是本文重点,不展开讲,相信这种小事难不倒聪明的你。
前面小编也有讲到pdf.js 相关文章:细品pdf.js实践解决含水印、电子签章问题「Vue篇」
于是乎,啪啪啪几行代码迅速搞定给产品演示。然后产品拿了个线上文件来尝试效果。。。
两人对着白屏尴尬的沉默良久,产品终于忍不住了。
“这怎么这么慢?不行,用户肯定不能接受。。。”
“公司网络不好... 你这文件太大了... 你重启一下试试?“
不存在的,作为一个优秀的前端开发者,怎么可以说这种话,当然是想办法解决啦。
重新整理一下产品的需求:
- 页面上查看服务器上的 pdf 文件
- 支持页码跳转、旋转、缩放
- 打开要快
基本上前两条上述方案都能满足,所以我们需要解决的关键问题在于如何让用户快速打开内容,减少等待时间。由于现有方案都是将 pdf 文件内容全部下载完成之后才开始进行渲染,如果文件比较大的时候,用户第一次打开时就可能需要等待很长时间。那么思路有了:我们可不可以不下载全部的文件内容就开始渲染?
方案思路 - PDF 内容分片加载
因为用户不可能一眼看到所有的 PDF 内容,每次只能看到屏幕显示范围内的几页。所以我们可以将可视范围内的PDF 页面内容优先下载并展示,可视范围外的我们根据用户浏览的实际位置按需下载和渲染。这样就可以减少第一次打开时用户的等待时间了。(类似与数据分页、图片懒加载的思想,目的是提高首屏性能。)
那么我们可以将一个大的 PDF 文件分成多个小文件,即分片。比如某个 PDF 有 200 页,我们按照 5 页一片,将它切分成 40 片,每次只下载用户看到的那一个分片。然后在用户进行滚动翻页的时候,异步的去下载对应包含对应页的分片。
基本的思路有了,接下来就是想办法实现了。要实现分片加载我们需要做两件事情:
1、服务器对 PDF 文件进行分片
由于这个是服务器做了,所以,交给后端就好了。本文不细讲,大家有兴趣的可以去了解 itextpdf (https://api.itextpdf.com/iText5/java/5.5.11/) 库,它提供了相关 API 对 PDF 进行切片。
我们需要跟后端约定好 PDF 文件分片之后每一片的数据格式。假如分片的大小为 5(即每次请求 5 页内容),那么可以定义数据格式如下:
{ "startPage": 1, // 分片的开始页码 "endPage": 5, // 分片结束页码 "totalPage": 100, // pdf 总页数 "url": "http://test.com/asset/fhdf82372837283.pdf" // 分片内容下载地址}
2、客户端根据用户交互行为获取并渲染指定的分片
显然,获取并渲染是两个操作。为了保证用户操作(滚动)的流畅性,这两个操作我们都异步进行。至此,我们需要解决的关键问题变成两个:
- 如何下载 PDF 分片
- 如何渲染 PDF 分片
知识准备 - PDF.js 接口介绍
由于我们无法在已有标签上做修改,所以我们考虑基于 PDF.js 库进行深度定制。那么我们先了解一下 PDF.js 可以为我们提供哪些能力。参考 官方文档(https://mozilla.github.io/pdf.js),下面列举了我们需要用到的几个 API ,由于官方文档中内容比较粗,这里贴上了源码