基于 pdf.js 的前端 PDF 預(yù)覽方案

產(chǎn)品需求描述

后端返回 pdf 文件鏈接蚯姆,前端預(yù)覽,要求不允許用戶下載洒敏、復(fù)制、打印郭毕。

初步方案

  1. 瀏覽器支持 pdf 文件預(yù)覽功能,通過 window.open 的方式打開新的鏈接函荣,效果如下:

問題:瀏覽器提供的下載显押、打印控件以及復(fù)制內(nèi)容扳肛、右鍵下載等操作無法干預(yù)

  1. 以 iframe 的方式加載文件乘碑,并禁用 iframe 的右鍵:
<iframe ref="iframe" :src="pdfUrl" />

網(wǎng)上找到的方案大多為 document.oncontextmenu = function() { return false; }兽肤,但實測發(fā)現(xiàn)該方法僅適用于子頁面內(nèi)容沒加載之前,如果資源加載完成則右鍵操作由子頁面本身控制资铡。

思路:在 iframe 加載成功后,為子頁面注冊對應(yīng)的事件處理函數(shù)尖飞。

let iframe = this.$refs.iframe
iframe.onload = () => {
    window.frames[0].contentDocument.oncontextmenu = () => false
}

問題:提示跨域

原因分析:網(wǎng)頁地址與資源鏈接的域名不一致葫松,導(dǎo)致 iframe 跨域底洗。

解決方案:由后端配置同源頭解決跨域問題咕娄,但使用 iframe 無法解決用戶復(fù)制文字的問題。

  1. 使用 embed 標(biāo)簽费变,禁止右鍵:
<embed :src="pdfUrl" enableContextMenu="false" />
<!-- 或者 -->
<embed :src="pdfUrl" oncontextmenu="window.event.returnValue=false" />

問題:僅在音視頻資源下生效

思考

分析

以上方案無法解決問題的原因在于利用了瀏覽器的默認(rèn)特性挚歧,且這些特性是無法干預(yù)的吁峻。因此需要轉(zhuǎn)換思路,將不可控的特性轉(zhuǎn)換為已有的可控特性矮慕。

思路

利用圖片無法選擇復(fù)制的特性啄骇,將 pdf 轉(zhuǎn)成圖片,并限制用戶無法右鍵保存痪寻。

解決方案

基于 pdf.js 將 pdf 按頁轉(zhuǎn)換為一張張圖片,通過 img 標(biāo)簽渲染蛇尚,并禁用右鍵和圖片拖拽猫态。

Step1 讀取 pdf 內(nèi)容

window.pdfjsLib.getDocument(pdfUrl)

由于資源鏈接不在本地,pdf.js 會報跨域的錯誤勇凭。

方案:參考該鏈接虾标,需要手動修改 pdf.js 的邏輯灌砖,并要求服務(wù)端配合解決跨域的問題。

修改 pdf.js 邏輯不利于后期升級和維護蘸吓,因此我們換一種思路:基于 ajax 請求撩幽。

axios.request({
    url: imgUrl,
    type: 'get',
    responseType: 'blob'
}).then(res => {
    window.pdfjsLib.getDocument(res.data)
})

成功解決跨域問題,并返回了 blob 對象宪萄,但在初始化 pdf.js 時報了如下錯誤:

查詢源碼得知拜英,pdf.js 不支持讀取 blob 對象琅催,因此需要將 blob 轉(zhuǎn)為 url:

window.pdfjsLib.getDocument(window.URL.createObjectURL(res.data))

Step2 解析文件,渲染到 canvas

調(diào)用 pdf.js 的 api 進行解析:

window.pdfjsLib.getDocument(window.URL.createObjectURL(res.data)).promise.then(pdf => {
    // 解析第一頁
    pdf.getPage(1).then(page => {
        let scale = 1
        let viewport = page.getViewport({ scale })
    })
})

渲染到 canvas:

pdf.getPage(1).then(page => {
    let scale = 1
    let viewport = page.getViewport({ scale })
    let canvas = this.$refs.canvas
    let context = canvas.getContext('2d')
    canvas.width = viewport.width
    canvas.height = viewport.height
    let renderContext = {
        canvasContext: context,
        viewport: viewport
    }
    page.render(renderContext)
})

Step3 渲染圖片

<img :src="pdfUrl" />
page.render(renderContext)
this.pdfUrl = canvas.toDataURL('image/png')

Step4 禁止右鍵和復(fù)制

<img :src="pdfUrl" :draggable="false" oncontextmenu="return false;" />

Step5 將 pdf 的每一頁轉(zhuǎn)換為圖片

上述步驟已經(jīng)完成大體邏輯,但在 step2 中只是將 pdf 的第一頁解析成了圖片杰捂。實際需求需要解析每一頁,然后通過輪播的方式顯示圖片挨队。因此需要做以下改造:

<Carousel v-if="pdfImgsShow">
    <CarouselItem v-for="(item, index) in pdfImgs" :key="index">
        <img :src="item" :draggable="false" oncontextmenu="return false;" />
    </CarouselItem>
</Carousel>
window.pdfjsLib.getDocument(window.URL.createObjectURL(res.data)).promise.then(async pdf => {
    for(let i = 1; i <= pdf.numPages; i++) {
        let page = await pdf.getPage(i)
        let scale = 1
        let viewport = page.getViewport({ scale })
        let canvas = this.$refs.canvas
        let context = canvas.getContext('2d')
        canvas.width = viewport.width
        canvas.height = viewport.height
        let renderContext = {
            canvasContext: context,
            viewport: viewport
        }
        page.render(renderContext)
        this.pdfImgs.push(canvas.toDataURL('image/png'))
    }
    this.pdfImgsShow = true
})

如下圖所示盛垦,已成功生成圖片:

但當(dāng) pdf 頁數(shù)大于 1 時腾夯,控制臺會報如下錯誤:

查詢源碼得知,page.render 方法是異步函數(shù)班利,在循環(huán)體內(nèi)部調(diào)用 render 方法會導(dǎo)致同時存在多個未執(zhí)行完的 render榨呆,引發(fā)上述錯誤积蜻。

解決方法:

await page.render(renderContext).promise
this.pdfImgs.push(canvas.toDataURL('image/png'))

Step6 調(diào)整清晰度

實際測試發(fā)現(xiàn),canvas 導(dǎo)出的圖片清晰度較差宙拉。
查詢資料得知是因為 dpi 的問題丙笋,參考該文章,調(diào)整 canvas 畫布的大小:

let UNITS = 2
canvas.width = Math.floor(viewport.width * UNITS)
canvas.height = Math.floor(viewport.height * UNITS)
let renderContext = {
    transform: [UNITS, 0,0, UNITS, 0, 0],
    canvasContext: context,
    viewport: viewport
}

完整代碼

<Carousel v-if="pdfImgsShow">
    <CarouselItem v-for="(item, index) in pdfImgs" :key="index">
        <img :src="item" :draggable="false" oncontextmenu="return false;" />
    </CarouselItem>
</Carousel>
<canvas ref="canvas" style="display: none;" />
axios.request({
     url: imgUrl,
     type: 'get',
     responseType: 'blob'
}).then(res => {
    window.pdfjsLib.getDocument(window.URL.createObjectURL(res.data)).promise.then(async pdf => {
        let UNITS = 2
        for(let i = 1; i <= pdf.numPages; i++) {
            let page = await pdf.getPage(i)
            let scale = 1
            let viewport = page.getViewport({ scale })
            let canvas = this.$refs.canvas
            let context = canvas.getContext('2d')
            canvas.width = Math.floor(viewport.width * UNITS)
            canvas.height = Math.floor(viewport.height * UNITS)
            let renderContext = {
                transform: [UNITS, 0,0, UNITS, 0, 0],
                canvasContext: context,
                viewport: viewport
            }
            await page.render(renderContext).promise
            this.pdfImgs.push(canvas.toDataURL('image/png'))
            context.clearRect(0, 0, viewport.width, viewport.height)
            this.pdfImgsShow = true
       }
    })
})
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市灶似,隨后出現(xiàn)的幾起案子瑞你,更是在濱河造成了極大的恐慌,老刑警劉巖者甲,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嫩实,居然都是意外死亡窥岩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門晃洒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來球及,“玉大人集歇,你說我怎么就攤上這事〖始撸” “怎么了姑蓝?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵纺荧,是天一觀的道長。 經(jīng)常有香客問我宙暇,道長,這世上最難降的妖魔是什么桃熄? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任瞳收,我火速辦了婚禮厢汹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘界弧。我一直安慰自己,他們只是感情好咽瓷,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布舰讹。 她就那樣靜靜地躺著,像睡著了一般钻洒。 火紅的嫁衣襯著肌膚如雪锄开。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音癣诱,去河邊找鬼。 笑死鲫惶,一個胖子當(dāng)著我的面吹牛实抡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赏淌,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼猜敢,長吁一口氣:“原來是場噩夢啊……” “哼盒延!你這毒婦竟也來了鼠冕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤计露,失蹤者是張志新(化名)和其女友劉穎票罐,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體该押,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蚕礼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年奠蹬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冀痕。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡狸演,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出猜极,到底是詐尸還是另有隱情消玄,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站兔跌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏华望。R本人自食惡果不足惜仅乓,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一夸楣、第九天 我趴在偏房一處隱蔽的房頂上張望子漩。 院中可真熱鬧石洗,春花似錦、人聲如沸缕棵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至矿辽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間雕蔽,已是汗流浹背宾娜。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工前塔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人食零。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓寂屏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吱抚。 傳聞我的和親對象是個殘疾皇子考廉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容