此文已由做者吳家聯受權網易雲社區發佈。
css
歡迎訪問網易雲社區,瞭解更多網易技術產品運營經驗。html
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來實現。ios
2. 官方pdfjs viewer分析
git
按照pdfjs官網上的介紹,pdfjs代碼是這樣分層的:github
固然由於迭代時間的關係,咱們並無研究core和display內部的東東,由於這一塊涉及到pdf文件格式規範。咱們把重點放在了viewer層。官方提供的viewer層組件是能夠直接使用的,功能也很豐富(demo地址:https://mozilla.github.io/pdf.js/web/viewer.html)。本來是喜滋滋的事情,開發都以爲不用幹什麼活了,可是產品卻說這不是咱們想要的......由於.......功能太多了,並且跟交互稿長得徹底不同。web
好吧,其實產品須要的功能不多,並且交互上有些差異。最終實現的修改和官方組件的差別以下圖所示:。canvas
(上邊是官方,下邊是教育)api
若是咱們在官方組件的基礎上作修改、作配置,那實在是很蛋疼,並且會依賴不少不必的代碼。因此我的決定參照官方viewer的代碼,實現一個簡單的、符合產品需求的viewer組件(因此本文標題是pdfjs viewer開發小結)。爲何說是「參照」呢?由於pdfjs提供的文檔(core和display層)實在太簡單了,單看文檔中的api,不少仍是沒法知道其功能和用處,看來官方是建議直接使用組件,不建議本身再開發一個viewer......。promise
實際大概花了一天閱讀了官方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和其餘安卓瀏覽器幾乎沒出現崩潰,若是真是庫實現問題也很難查了。
通過一成天的排查,結論是一、二、3推測都不能成立,4的話很差說。最後實在想不出辦法了就一點一點看崩潰的現象,而後就在網絡代理的抓包信息中發現一個奇怪的現象:崩潰以前,庫已經加載完畢,開始加載pdf文件了,可是pdf文件加載到一半就崩潰了,想來想去,猜想是否是加載環節出的問題,由於我直接使用的api中的getDocument方法加載文件,並不清楚內部實現。因而本身寫了個簡單的xhr加載文件方法,不使用庫的api,再測試就不崩潰了。。。真是要淚奔。
不過目前正真崩潰的緣由仍是不清楚,只找到這一種解決方法。getDocument中應該實現了range加載邏輯(雖然實際測試中沒有發現),並不建議本身實現加載方法,正常來講是畫蛇添足,不過面對x5這樣的問題也只能用不正常的方案了。
3. 移動端手勢的一些問題
目前移動端手勢問題主要是在縮放時出現的,部分手機瀏覽器沒法阻止用戶縮放,致使放大pdf時整個頁面都放大了,這樣看起來會很奇怪,雖然經過代碼寫了meta,也阻止了部分touch事件,可是某些手機仍是偶爾會出現,很頭大。不知道有沒有同事在這方面有經驗,能夠指導一下。
網易雲免費體驗館,0成本體驗20+款雲產品!
更多網易技術、產品、運營經驗分享請點擊。
相關文章:
【推薦】 適配的那些事
【推薦】 Android TV 開發(2)