pdfjs viewer 开发小结
此文已由作者吴家联授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
1. pdfjs库简介
PDF.js 是由Mozilla 主导推出的可以将PDF文件转换为H5页面进行展示的工具。相比较目前前端可以用的pdf节点方案,pdfjs是非常合适的。它有这么几个优点:1.完全js开发,不依赖其他js库,不使用flash插件。2.代码分层做的较好,官方提供了可以直接使用的封装组件,无需额外开发。3.兼容性也不错,支持canvas和svg渲染,pc和手机端都可以使用。教育这边pc端pdf reader是使用flash开发的,这次产品打算做手机上h5的pdf功能,所以前端决定使用pdfjs来实现。
2. 官方pdfjs viewer分析
按照pdfjs官网上的介绍,pdfjs代码是这样分层的:
当然因为迭代时间的关系,我们并没有研究core和display内部的东东,因为这一块涉及到pdf文件格式规范。我们把重点放在了viewer层。官方提供的viewer层组件是可以直接使用的,功能也很丰富(demo地址:https://mozilla.github.io/pdf.js/web/viewer.html)。原本是喜滋滋的事情,开发都觉得不用干什么活了,但是产品却说这不是我们想要的......因为.......功能太多了,而且跟交互稿长得完全不一样。
好吧,其实产品需要的功能很少,而且交互上有些差别。最终实现的修改和官方组件的差异如下图所示:。
(上边是官方,下边是教育)
如果我们在官方组件的基础上做修改、做配置,那实在是很蛋疼,而且会依赖很多没必要的代码。所以个人决定参照官方viewer的代码,实现一个简单的、符合产品需求的viewer组件(所以本文标题是pdfjs viewer开发小结)。为什么说是“参照”呢?因为pdfjs提供的文档(core和display层)实在太简单了,单看文档中的api,很多还是无法知道其功能和用处,看来官方是建议直接使用组件,不建议自己再开发一个viewer......。
实际大概花了一天阅读了官方viewer的代码。梳理了一下流程和重点的类:
因为产品需要的功能比较少,所以只看了最基本的功能代码,涉及的类不多,具体有这么几个:
pdf_page_view.js:是viewer层最重要的类,封装的是pdf每一页的功能,实现的功能有:设置或者更新pdf page、更新page的缩放倍数和旋转角度、绘制page和管理绘制的状态等。类里面的部分代码是可以去掉的,比如:如果只需要支持canvas绘制,则可以去掉svg绘制部分的代码,如果不需要文本层选中功能,可以去掉textlayer部分的代码。如下图所示的方法:
另外一个重点是page绘制的状态,状态常量有:
INITIAL: 0
RUNNING: 1
PAUSED: 2
FINISHED: 3
Page实例化或者取消绘制时状态变为INITIAL,当调用draw方法时,状态变为RUNNING,这时会调用api中的render方法,返回一个任务对象,在进行绘制时任务对象会触发onContinue回调,并暂停绘制,表示绘制正在进行,是否继续,这时可以处理一些逻辑,比如当用户快速翻页时,当前page还未绘制完就翻下一页了,此时可以在onContinue回调中选择不继续绘制,把page的状态变为PAUSED,当下次翻到该页时再继续绘制。绘制完全完毕后状态变为FINISHED。当然绘制过程中也可能报错,其中的处理都是通过promise实现的。page在进行缩放或者旋转时一般需要重新绘制,重新绘制可以保证清晰度,这时状态也会重置,重新走一遍。需要注意的是在不同浏览器中canvas可以绘制的像素大小上限是不同的,官方取的应该是ie中的上限(几个主流浏览器中的最小值),如果缩放到达上限就不能继续重新绘制了,可能引起浏览器崩溃,只能通过css手段来模拟,但是不能保证清晰度。
pdf_rendering_queue.js:管理page的绘制,比如确定绘制的优先级、预先绘制功能等。如果自己实现这个类可以简化,因为交互形式不同,不需要处理一些情况。
pdf_viewer.css:样式文件注意是实现了page的样式,也就是每一页相关的样式,这个不用多讲。
pdf_viewer.js:处理了pdf文档对象,创建和维护page_view实例,对外提供接口,比如翻页、缩放、旋转等。流程是这样的:从pdf对象里面取出第一页的viewport,做为默认的viewport,用这个默认的viewport实例化全部的page_view对象,之后调用update方法,渲染优先级最高的page,当用户翻页或者其他操作时又会重复update的流程。里面还实现了一个简单的cache功能,将已经绘制完的page对象push到cache队列,队列超过一定长度就删除开头的page,来保证内存消耗不会过大。
ui_utils.js:包含了一些常量和工具方法,比如page滚动功能、全局自定义事件管理、像素处理方法(本人对canvas没有研究,相关代码没有进行解读)等;
3. 教育产品pdfjs viewer的设计
以上分析的几个类里面的功能已经可以满足产品需求了,整理一下时序图:
当然在viewer基础上我们再封装了一层regular组件(教育产品组件规范)作为与用户交互的部分。
4. 踩坑记录(几个大问题)
1. 缓存的限制
官方viewer中设置cache大小(缓存的已经绘制的page_view实例个数)默认是10,在手机上不建议设置这么多,测试发现某些浏览器,比如安卓中的UC,多个page都放大的情况下可能会导致崩溃,或者快速翻页的时候也可能,当然概率很小。
2. 微信中的崩溃问题【重点】
在测试过程中发现安卓微信中打开pdf会崩溃,而且出现的概率很大,如果无法解决根本不能上线,当时真是整个人都要崩溃了...微信x5浏览器果然是新时代的ie6…。排查过程也只能靠猜了,ie6至少网上查查信息还蛮多的,你个x5的信息根本查不到,即使查到有人在微信论坛反馈问题也不一定有人去解决。
猜测可能的原因有这么几个:1.跟pdf文件大小有关,比如文件太大会引起崩溃,2.跟pdf内容有关,比如包含某些内容会引起崩溃,3.pdfjs库依赖的方式有问题,这个可能性很小,4.pdfjs内部实现问题,但是ios和其他安卓浏览器几乎没出现崩溃,如果真是库实现问题也很难查了。
经过一整天的排查,结论是1、2、3推测都不能成立,4的话不好说。最后实在想不出办法了就一点一点看崩溃的现象,然后就在网络代理的抓包信息中发现一个奇怪的现象:崩溃之前,库已经加载完毕,开始加载pdf文件了,但是pdf文件加载到一半就崩溃了,想来想去,猜测是不是加载环节出的问题,因为我直接使用的api中的getDocument方法加载文件,并不清楚内部实现。于是自己写了个简单的xhr加载文件方法,不使用库的api,再测试就不崩溃了。。。真是要泪奔。
不过目前正真崩溃的原因还是不清楚,只找到这一种解决方法。getDocument中应该实现了range加载逻辑(虽然实际测试中没有发现),并不建议自己实现加载方法,正常来说是多此一举,不过面对x5这样的问题也只能用不正常的方案了。
3. 移动端手势的一些问题
目前移动端手势问题主要是在缩放时出现的,部分手机浏览器无法阻止用户缩放,导致放大pdf时整个页面都放大了,这样看起来会很奇怪,虽然通过代码写了meta,也阻止了部分touch事件,但是某些手机还是偶尔会出现,很头大。不知道有没有同事在这方面有经验,可以指导一下。
网易云免费体验馆,0成本体验20+款云产品!
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 【大数据之数据仓库】GreenPlum PK DeepGreen(TPCH)
【推荐】 白木彰:具有普遍性的设计力
pdfjs viewer 开发小结的更多相关文章
- vue开发小结(下)
前言 继前几天总结了vue开发小结(上)后,发现还有很多的点没有能列举出来,于是还是打算新建一个下篇,再补充一些vue开发中需要注意的细节,确实还是都是细节的问题,我只是在这里强调下,希望对大家有帮助 ...
- Android 即时通讯开发小结(二)
<Android 即时通讯开发小结>基于IM Andriod 开发的各种常见问题,结合网易云信即时通讯技术的实践,对IM 开发做一个全面的总结. 相关推荐阅读:. Android 即时通讯 ...
- Android 即时通讯开发小结(一)
<Android 即时通讯开发小结>基于IM Andriod 开发的各种常见问题,结合网易云信即时通讯技术的实践,对IM 开发做一个全面的总结. 相关推荐阅读:. Android 即时通讯 ...
- 5Spring动态代理开发小结
5Spring动态代理开发小结 1.为什么要有动态代理? 好处 1.利于程序维护 2.利于原始类功能的增强 3.得益于JDK或者CGlib等动态代理技术使得程序扩展性很强 为什么说使得程序扩展性很强? ...
- 无插件的大模型浏览器Autodesk Viewer开发培训-武汉-2014年8月28日 9:00 – 12:00
武汉附近的同学们有福了,这是全球第一次关于Autodesk viewer的教室培训. :) 你可能已经在各种场合听过或看过Autodesk最新推出的大模型浏览器,这是无需插件的浏览器模型,支持几十种数 ...
- 移动Web开发小结
以下是做移动端Web开发过程中小结的几个事项:希望能够帮助到大家,同时也方便自己查看: 1,在移动开发页面中,主体盒子的max-width与min-width的设置原因: ①设置max-width是为 ...
- ipad开发小结
项目小结 :布局的时候最后要用CGRectDivi.. :控制器的生命周期---->(init-->(当self.view==nil调用 loadView viewdidload)--&g ...
- redis开发小结
随着缓存在web服务中用的越来越广泛,redis可以说成为了目前最流行的NoSQL数据库!redis与memcached最大的不同在于redis支持更多的数据类型,包括string.hash.list ...
- H5嵌入原生开发小结----兼容安卓与ios的填坑之路
一开始听说开发H5,以为就是做适配现代浏览器的移动网页,心想不用管IE了,欧也.到今天,发现当初too young too simple,兼容IE和兼容安卓与IOS,后者让你更抓狂.接下来数一下踩过的 ...
随机推荐
- Android APK加壳技术方案
Android APK加壳技术方案[1] Android APK加壳技术方案[2]
- 7.1 Java集合概述
List 有序.重复的集合 Set 无序.不可重复的集合 Map 具有映射关系的集合 jdk1.5之后.Java增加了Queue体系集合,代表一种队列集合实现
- 494 Target Sum 目标和
给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S.现在你有两个符号 + 和 -.对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面.返回可以使最终数组和为 ...
- Hash环/一致性Hash原理
当前,Memcached.Redis这类分布式kv缓存已经非常普遍.从本篇开始,本系列将分析分布式缓存相关的原理.使用策略和最佳实践. 我们知道Memcached的分布式其实是一种“伪分布式”,也就是 ...
- [转]VC++中对文件的写入和读取
本文转自:http://blog.csdn.net/fanghb_1984/article/details/7425705 本文介绍两种方法对文件进行读取和写入操作:1.采用fstream类:2.采用 ...
- 序列化shelve模块
1.shelve对pickle进行封装,所以shelve也只能在python里使用. shelve可以进行多次dump而且顺序不会乱. import shelve f = shelve.open('s ...
- codeforces415D. Glad to see you!(交互)
题意 交互题. 有$k$个值域为$[1, n]$的数. 请在不超过$60$次询问内找出其中的两个数. 每次询问形式为1 x y 交互库会返回$|x - a| <= |y - b| ? " ...
- iOS 随笔 允许所有不安全网络访问项目
允许任意请求访问app App Transport Security Settings -> Allow Arbitrary Loads YES
- 版本号比较versioncompare方法,java实现
测试
- Understanding Scroll Views 深入理解 scroll view 读书笔记
Understanding Scroll Views 深入理解 scroll view 读书笔记 It may be hard to believe, but a UIScrollView is ...