背景
在項(xiàng)目中使用pdfjs渲染頁面,出現(xiàn)縮放和dpr異常称杨,表現(xiàn)為文檔內(nèi)容等比縮放到頁面左上部分绰沥,且上下顛倒瓦宜。
問題排查
問題出現(xiàn)之前蔚万,使用的渲染方式是getDocument完成后,逐頁面循環(huán)調(diào)用page.render临庇,直到所有頁面都加載完成反璃。為了節(jié)省資源,引入 Intersection Observer以后假夺,發(fā)現(xiàn)此問題出現(xiàn)淮蜈。
查找資料得知,出現(xiàn)此問題的原因99%都是因?yàn)椴⑿胁僮鱟anvas導(dǎo)致的已卷。
現(xiàn)在邏輯是每次收到Observer回調(diào)梧田,就調(diào)一次渲染單頁(先設(shè)置canvas寬高,再調(diào)用page.render)侧蘸。Intersection Observer有時(shí)會反復(fù)針對某一頁面進(jìn)行回調(diào)裁眯,雖然我們已經(jīng)使用了一個(gè)set進(jìn)行標(biāo)記加鎖防止重復(fù)渲染,但是只保護(hù)了page.render的調(diào)用讳癌,漏掉了設(shè)置canvas尺寸的邏輯穿稳,這就導(dǎo)致了在page.render過程中依然可以設(shè)置了canvas寬高的情況,也就出現(xiàn)了開頭這種異常情況晌坤。將canvas尺寸修改也加到保護(hù)范圍后逢艘,問題解決。
解決方法
想辦法確保不要同時(shí)操縱canvas骤菠,包括page.render和修改canvas height/width等它改。此處使用的方案是一個(gè)set,渲染中將pageNum放到set中商乎,每次請求渲染前檢查page是否在set中央拖。注意,這只是個(gè)簡單的實(shí)現(xiàn),極端情況下依然可能存在競爭的情況爬泥。
偽代碼如下:
const busyPageSet = new Set();
const renderPage = async (pageNum) => {
// 關(guān)鍵代碼柬讨,判斷當(dāng)前頁面是否被標(biāo)記為正在渲染狀態(tài)
if (busyPageSet.has(pageNum)) {
return;
}
busyPageSet.add(pageNum); // 標(biāo)記當(dāng)前頁面為渲染狀態(tài)
const page = await pdfDoc.getPage(pageNum);
const viewport = page.getViewport(); // 并處理scale和dpr等縮放邏輯
const canvas = getCanvasRef(pageNum); // 獲取canvas
canvas.width = ...; // 設(shè)置canvas高度
await page.render({context: ..., viewport: ...}).promise; // 調(diào)用pdfjs渲染canvas
busyPageSet.delete(pageNum); // 解除標(biāo)記當(dāng)前頁面的渲染狀態(tài)
}
補(bǔ)充
當(dāng)重復(fù)調(diào)用page.render時(shí),pdfjs會在控制臺輸出告警袍啡。但是若渲染時(shí)進(jìn)行了canvas的尺寸修改踩官、直接調(diào)用draw命令,pdfjs不會發(fā)出告警境输。
本次問題主要原因是加鎖保護(hù)時(shí)蔗牡,設(shè)置的保護(hù)范圍不全面,漏掉了其他canvas操作嗅剖,而這又不會觸發(fā)告警辩越,導(dǎo)致排查的時(shí)間有點(diǎn)長,值得吸取經(jīng)驗(yàn)信粮。
參考資料
https://stackoverflow.com/questions/44885973/pdf-js-document-is-presented-upside-down-randomly
https://github.com/mozilla/pdf.js/issues/11277#issuecomment-546498526