国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

js實現(xiàn)PDF 預覽和文件下載

這篇具有很好參考價值的文章主要介紹了js實現(xiàn)PDF 預覽和文件下載。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

在開發(fā)過程中要求對 PDF 類型的發(fā)票提供 預覽下載 功能,PDF 類型文件的來源又包括 H5 移動端PC 端,而針對這兩個不同端的處理會有些許不同,下文會有所提及。

針對 PDF 預覽 的文章不在少數(shù),但似乎都沒有提及可能遇到的問題,或是提供對應的具體需求場景下如何選擇,因此,本文的核心就是結(jié)合實際需求場景下,看看目前各種實現(xiàn)方案到底哪一個更適合,當然希望大家可以在評論區(qū)對文中的內(nèi)容進行斧正,或是提供更優(yōu)質(zhì)的方案。

基本要求:

  • 支持 pdf 文件 內(nèi)容的 完整預覽
  • 多頁 pdf 文件 支持 分頁查看
  • PC 端移動端 都需支持 下載預覽

產(chǎn)品要求:

  • PC 端 的預覽要支持在 當前頁 進行預覽
  • pdf 文件 預覽時的字體要 和 實際文件的 字體保證一致性

PDF 預覽

先拋開上面的各種要求,咱們先總結(jié)下目前實現(xiàn) PDF 預覽的幾種常用方式:

  • 借助各種類庫,基于代碼實現(xiàn)預覽,如基于 pdfjs-dist 的包
  • 直接基于各個瀏覽器內(nèi)置的 PDF 預覽插件,如 <iframe src="xxx">、<embed src="xxx" >
  • 服務端將 PDF 文件轉(zhuǎn)換成圖片

接下來分別看看以上方案如何實現(xiàn),以及是否符合上述提供的要求!

<embed> / <iframe> 實現(xiàn)預覽

<embed> 標簽

<embed> 元素 將外部內(nèi)容嵌入文檔中的指定位置,此內(nèi)容由 外部應用程序其他交互式內(nèi)容源(如 瀏覽器插件)提供。

說簡單點,就是使用 <embed> 來展示的資源是完全交由它所在的環(huán)境提供的展示功能,即如果當前的應用環(huán)境支持這個資源的展示那么就可以正常展示,如果不支持那就無法展示。

使用起來也是非常簡單:

<embed  type="application/pdf"  :src="pdfUrl"  width="800"  height="600" />

js pdf文件下載,微信小程序,javascript,pdf,前端

多數(shù)現(xiàn)代瀏覽器已經(jīng)棄用并取消了對瀏覽器插件的支持,現(xiàn)在已經(jīng)不建議使用 <embed> 標簽,但可以使用 <img>、<iframe>、<video>、<audio> 等標簽代替。

<iframe> 標簽

基于 <iframe> 的方式和以上差不多,整體效果也一致,這里這就不在額外展示:

<iframe  :src="pdfUrl"  width="800"  height="600" />

值得注意的是,即便使用的是 <iframe> 但實際展開其內(nèi)層結(jié)構(gòu)后你會發(fā)現(xiàn):

js pdf文件下載,微信小程序,javascript,pdf,前端

其內(nèi)部還是 <embed> 標簽?這是怎么回事,不是說最好不建議使用 <embed> 嗎?

首先來在 caniuse 查看兼容情況,如下:

js pdf文件下載,微信小程序,javascript,pdf,前端

我們再找一個不支持 <embed> 的瀏覽器,比如 IE,來試試效果:

js pdf文件下載,微信小程序,javascript,pdf,前端

換成 <iframe> 試試,如下:

js pdf文件下載,微信小程序,javascript,pdf,前端

顯然,<embed> 在不兼容的環(huán)境直接無法顯示,而 <iframe> 是能夠正常識別的,只不過 <iframe> 加載的資源無法被 IE 瀏覽器處理,即本質(zhì)原因是 IE 瀏覽器根本就不支持對類似 PDF 等文件的預覽,比如當嘗試直接在地址欄中輸入 http://127.0.0.1:3000/src/assets/2.pdf 時會得到:

js pdf文件下載,微信小程序,javascript,pdf,前端

因此,通常情況下當瀏覽器不支持內(nèi)聯(lián) PDF 時,應該提供一個 PDF 的回退鏈接,即以下載的方式來實現(xiàn),而這就是 pdfobject 做的事情,實際上它的源碼內(nèi)容比較簡單,核心就是 PDFObject 會檢測瀏覽器對內(nèi)聯(lián)/嵌入 PDF 的支持,如果支持嵌入,則嵌入 PDF,如果瀏覽器不支持嵌入,則不會嵌入 PDF,并提供一個指向 PDF 的回退鏈接,例如在 IE 中的表現(xiàn):

js pdf文件下載,微信小程序,javascript,pdf,前端

事實上,這其實只是幫我們少寫了一些兼容性的代碼而已,也不一定符合大部分人的場景,在這里提到只是因為其與 <embed> 之間存在的聯(lián)系。

vue3-pdfjs 實現(xiàn)預覽

為什么不直接使用 pdfjs-dist?

js pdf文件下載,微信小程序,javascript,pdf,前端

pdf.js 幾個明顯的可吐槽的點:

  • 包名稱不統(tǒng)一,npm 上的包名叫 pdfjs-dist,然而在 Readme 中自己又稱其為 pdf.js
  • 沒有清晰的文檔作為指引,只能通過其倉庫中的 examples 目錄的內(nèi)容作為參考
  • 官方示例不夠友好,例如沒有提供 vue/react 等相關(guān)的示例
  • 直接使用需要引入很多文檔沒有指明的內(nèi)容
  • 有時展示的 pdf 內(nèi)容文字模糊或缺少部分等

因此,既然已經(jīng)有基于 vue/react 封裝好的包,這里就直接用來作為演示。

具體使用

安裝和使用過程可參考 vue3-pdfjs ,具體 Vue3 示例代碼如下:

js

復制代碼

<script setup lang="ts"> 
import { onMounted, ref } from 'vue' 
import { VuePdf, createLoadingTask } from 'vue3-pdfjs/esm' 
import type { VuePdfPropsType } from 'vue3-pdfjs/components/vue-pdf/vue-pdf-props' 
// Prop type definitions can also be imported 
import type { PDFDocumentProxy } from 'pdfjs-dist/types/src/display/api'
import pdfUrl from './assets/You-Dont-Know-JS.pdf'  
const pdfSrc = ref<VuePdfPropsType['src']>(pdfUrl) 
const numOfPages = ref(0) onMounted(() => {   
const loadingTask = createLoadingTask(pdfSrc.value)   
loadingTask.promise.then((pdf: PDFDocumentProxy) => {    
 numOfPages.value = pdf.numPages   }) }) </script> 
 
<template>
 <VuePdf v-for="page in numOfPages" :key="page" :src="pdfSrc" :page="page" /> 
</template> 
 
<style> @import '@/assets/base.css'; 
</style>

效果如下:

存在問題

看上去加載正常的 pdf 文檔 似乎沒啥大問題,來試試加載 pdf 發(fā)票 看看,但由于實際發(fā)票敏感信息較多,這里就不貼出原本的發(fā)票內(nèi)容,直接來看預覽后的發(fā)票內(nèi)容:

  • 顯然整體發(fā)票的 內(nèi)容缺失得非常多,雖然某些發(fā)票大部分能夠展示,但如 發(fā)票抬頭印章 部分可能無法正常顯示等

    js pdf文件下載,微信小程序,javascript,pdf,前端js pdf文件下載,微信小程序,javascript,pdf,前端

注意】無法顯示完整的內(nèi)容是因為 pdf.js 是需要一些字體庫的支持,如果 原 PDF 文件 中部分字體沒有匹配到字體庫將無法在 pdf.js 中顯示,而字體庫存放在 cmaps 文件夾下 js pdf文件下載,微信小程序,javascript,pdf,前端

  • 另外,預覽的字體實際的字體不一致 的,而由于發(fā)票的特殊性,對字體的一致性是有較大的要求,畢竟如果同一張發(fā)票字體不一致會缺乏 規(guī)范性 和 合法性(`被要求字體一致時的說法`js pdf文件下載,微信小程序,javascript,pdf,前端

常見的解決方案: 解決 pdf.js 無法完全顯示 pdf 文件內(nèi)容的問題,實際上還是根據(jù)執(zhí)行環(huán)境的錯誤信息進行分析,需要強行修改源碼內(nèi)容。

Mozilla Firefox(火狐瀏覽器)

Mozilla Firefox 內(nèi)置的 PDF 閱讀器實際就是 pdf.js,你可以直接用火狐瀏覽器預覽一下 pdf 文件,如下:

js pdf文件下載,微信小程序,javascript,pdf,前端

并且大多基于 pdf.js 二次封裝的庫 vue-pdf、vue3-pdfjs 等在預覽 pdf 文件的發(fā)票時通常無法顯示完整內(nèi)容,需要或多或少的涉及對源碼的更改,而在 Firefox 中內(nèi)置的 pdf.js 卻能夠完整的顯示對應的 pdf 文件的內(nèi)容。

js pdf文件下載,微信小程序,javascript,pdf,前端

PDF 轉(zhuǎn) 圖片 實現(xiàn)預覽

這種方式應該不用多說了,核心是服務端在響應 pdf 文件時,先轉(zhuǎn)換成圖片類型再返回,前端直接展示具體圖片內(nèi)容即可。

具體實現(xiàn)

下面通過用 node 來模擬:

const pdf = require('pdf-poppler') 
const path = require('path') 
const Koa = require('koa') 
const koaStatic = require('koa-static') 
const cors = require('koa-cors') 
const app = new Koa() 
// 跨域 
app.use(cors()) 
// 靜態(tài)資源 
app.use(koaStatic('./server')) 
function getFileName(filePath) {   
  return filePath.split('/').pop().replace(/\.[^/.]+$/, '') 
} 
function pdf2png(filePath) {   
// 獲取文件名   
const fileName = getFileName(filePath);   
const dir = path.dirname(filePath);   
// 配置參數(shù)   
const options = {     
format: 'png',     
out_dir: dir,     
out_prefix: fileName,     
page: null,   
}   
// pdf 轉(zhuǎn)換 png   
return pdf.convert(filePath, options).then((res) => {
  console.log('Successfully converted !')       
  return `http://127.0.0.1:4000${dir.replace('./server','')}/${fileName}-1.png`     }).catch((error) => {              console.error(error)     }) } 
  // 響應 
  app.use(async (ctx) => {     
  if(ctx.path.endsWith('/getPdf')){         
  const url = await pdf2png('./server/pdf/2.pdf')         
  ctx.body = { url }     
  }else{
  ctx.body = 'hello world!'     } }) 
  app.listen(4000)

避免踩一些坑

坑一:不推薦 pdf-image

在實現(xiàn)服務端將 pdf 文件轉(zhuǎn)換成圖片時需要依賴到一些第三方包,一開始使用了 pdf-image 這個包,但在實際轉(zhuǎn)換時發(fā)生較多的異常錯誤,順著錯誤查看源碼后發(fā)現(xiàn)其內(nèi)部需要依賴一些額外的工具,因為其中需要使用 pdfinfo xxx 相關(guān)命令,并且其對應的 issue 上也存在著一些類似問題,但都試了試最后還是沒有成功!

js pdf文件下載,微信小程序,javascript,pdf,前端

因此,更推薦使用 pdf-poppler 其中附帶了一個 pdftocairo 的程序可以實現(xiàn) pdf 到 圖片 的轉(zhuǎn)換能力,不過它目前版本支持 WindowsMac OS,如下:

js pdf文件下載,微信小程序,javascript,pdf,前端

坑二:path.basename not a function

在上述的代碼內(nèi)容中需要獲取文件的名稱,實際上我們可以簡單直接的使用 Node Apipath.basename(path[, suffix]) 來達到目的:

js pdf文件下載,微信小程序,javascript,pdf,前端

但是在程序運行時發(fā)生了如下 異常,對應的 代碼內(nèi)容 和 運行結(jié)果 如下:

js

復制代碼

// 配置參數(shù) const options = { format: 'png', out_dir: dir, out_prefix: path.baseName(filePath, path.extname(filePath)), // 發(fā)生異常 page: null, }

js pdf文件下載,微信小程序,javascript,pdf,前端

這個暫時沒有找到是什么原因,只能自己簡單實現(xiàn)了一個 getFileName 方法用于獲取文件的名稱。

報錯原因:太依賴編輯器的自動提示,將 basename 輸出成 baseName ,沒錯就是 n 和 N 的區(qū)別.

坑三:細節(jié)

上述內(nèi)容通過 koa 啟動模擬業(yè)務服務,由于 業(yè)務服務(http://127.0.0.1:4000應用服務 (http://127.0.0.1:3000) 間的端口不一致,因此會產(chǎn)生 跨域,此時可以通過 koa-cors 來解決,值得注意的是有時候的那個業(yè)務服務器重啟時 koa-cors 可能不起作用。

由于響應的內(nèi)容直接在 koa 通用中間件中返回,因此,如果你需要支持業(yè)務服務提供 靜態(tài)資源 的訪問能力,就可以通過 koa-static 來實現(xiàn),值得注意的是,當你通過 koa-static 指定靜態(tài)文件資源后,如 app.use(koaStatic('./static')),此時如果你直接通過 http://127.0.0.1:4000/static/pdf/xxx.png 時,那么會得到 404 Not Found 的錯誤,原因在于 koa-static 是直接把 /static/ 設置成了 根路徑,因此正確的訪問路徑為:http://127.0.0.1:4000/pdf/xxx.png

效果演示

發(fā)票內(nèi)容不方便展示這里就不直接展示了,只需要關(guān)注生成的圖片和路徑即可:

PDF 下載

這里的下載實際不僅指 pdf 的下載,而是客戶端方面所能支持的下載方式,最常見的如下幾種:

  • a 標簽,例如 <a href="xxxx" download="xxx">下載</a>
  • location.href,例如 window.location.href = xxx
  • window.open,例如 window.open(xxx)
  • Content-disposition,例如 Content-disposition:attachment;filename="xxx"

<a> 實現(xiàn)下載

<a>download 屬性用于指示瀏覽器 下載 href 指定的 URL,而不是導航到該資源,通常會提示用戶將其保存為本地文件,如果 download 屬性有指定內(nèi)容,這個值就會在下載保存過程中作為 預填充的文件名,主要是因為如下原因:

  • 這個值可能會通過 JavaScript 進行動態(tài)修改
  • 或者 Content-Disposition 中指定的 download 屬性優(yōu)先級高于 a.download

這種應該是大家最熟悉的方式了,但熟悉歸熟悉,還有一些值得注意的點:

  • download 屬性只適用于 同源 URL
    • 同源 URL 會進行 下載 操作
    • 非同源 URL 會進行 導航 操作
    • 非同源的資源 仍需要進行下載,那么可以將其轉(zhuǎn)換為 blob: URLdata: URL 形式
  • HTTP 響應頭中的 Content-Disposition 屬性中指定了一個不同的文件名,那么會優(yōu)先使用 Content-Disposition 中的內(nèi)容
  • HTTP 若 HTTP 響應頭中的 Content-Disposition 被設置為 Content-Disposition='inline',那么在 Firefox 中會優(yōu)先使用 Content-Dispositiondownload 屬性

靜態(tài)方式:

  <a href="http://127.0.0.1:4000/pdf/2-1.png" download="2.pdf">下載</a>

動態(tài)方式:

function download(url, filename){   
const a = document.createElement("a"); 
// 創(chuàng)建 a 標簽   
a.href = url; 
// 下載路徑   
a.download = filename;  
// 下載屬性,文件名   
a.style.display = "none"; // 不可見   
document.body.appendChild(a); // 掛載   
a.click(); // 觸發(fā)點擊事件   
document.body.removeChild(a); // 移除 
}

Blob 方式

if (reqConf.responseType == 'blob') {     
// 返回文件名     
let contentDisposition = config.headers['content-disposition'];     
if (!contentDisposition) {       
contentDisposition = `;filename=${decodeURI(config.headers.filename)}`;    
 }     
 const fileName = window.decodeURI(contentDisposition.split(`filename=`)[1]);     
 // 文件類型     
 const suffix = fileName.split('.')[1];     
 // 創(chuàng)建 blob 對象     
 const blob = new Blob([config.data], {       
 type: FileType[suffix],     });     
 const link = document.createElement('a');     
 link.style.display = 'none';     
 link.href = URL.createObjectURL(blob); 
 // 創(chuàng)建 url 對象     
 link.download = fileName; 
 // 下載后文件名     
 document.body.appendChild(link);     
 link.click();     
 document.body.removeChild(link); 
 // 移除隱藏的 a 標簽      
 URL.revokeObjectURL(link.href); 
 // 銷毀 url 對象   
 }

Content-disposition 和 location.href/window.open 實現(xiàn)下載

這看似是三種下載方式,但實際上就是一種,而且還是以 Content-disposition 為準。

Content-Disposition 響應頭 指示回復的內(nèi)容該以何種形式展示,是以 內(nèi)聯(lián) 的形式(即網(wǎng)頁或頁面的一部分)展示,還是以 附件 的形式 下載 并保存到本地,如下:

  • inline: 是 默認值,表示回復中的消息體會以頁面的一部分或者整個頁面的形式展示

    Content-Disposition: inline

  • attachment: 設置為此值意味著消息體應該被下載到本地,大多數(shù)瀏覽器會呈現(xiàn)一個 “保存為” 的對話框,并將 filename 的值預填為下載后的文件名

    Content-Disposition: attachment; filename="filename.jpg"

因此,基于 location.href='xxx'window.open(xxx) 的方式能實現(xiàn)下載就是基于 Content-Disposition: attachment; filename="filename.jpg" 的形式,又或者說是觸發(fā)了瀏覽器本身的下載行為,滿足了這個條件,無論是通過 a 標簽跳轉(zhuǎn)、location.href 導航、window.open 打開新頁面、直接在地址欄上輸入 URL 等都可以實現(xiàn)下載。

H5 移動端的下載

H5 移動端針對于 預覽 操作而言基于以上的方式都是可以實現(xiàn),但是 下載 操作可就不同了,因為這是要區(qū)分場景:

  • 基于 手機瀏覽器
  • 基于 微信內(nèi)置瀏覽器

基于 手機瀏覽器 的下載方式和上述提到的內(nèi)容大致上也是一致的,本質(zhì)上只要所在的客戶端支持下載那就沒有問題,然而在 微信內(nèi)置瀏覽器 中你使用常規(guī)的下載方式可能達不到預期:

  • Android 中使用常規(guī)的下載方式,通常會彈出對話框,詢問你是否需要喚醒 手機瀏覽器 來實現(xiàn)對應資源的下載,部分機型卻不會
  • IOS 中以上方式都 無法實現(xiàn)下載,因此通常情況下會打開一個新的 webview 來提供預覽,部分機型在新的頁面中支持 長按屏幕 的方式進行保存操作,但并不是所有機型都支持

本質(zhì)原因是在 微信內(nèi)置瀏覽器 中屏蔽任何的 下載鏈接,如 APP 的下載鏈接、普通文件 的下載鏈接 等等。

H5 移動端的下載還能怎么做?

由于這是 微信內(nèi)置瀏覽器 環(huán)境對下載功能的屏蔽,因此 不用再考慮(`想都不敢想`)基于 微信內(nèi)置瀏覽器 來實現(xiàn)下載功能,轉(zhuǎn)而應該考慮的是如何實現(xiàn) 間接下載

  • 判斷當前是否是屬于 微信內(nèi)置瀏覽器,若是則幫助用戶自動喚起 手機瀏覽器 實現(xiàn)下載,但并不是所有機型都支持 喚起 操作,因此最好是提示使用用戶直接通過 手機瀏覽器 實現(xiàn)下載,為了方便用戶,可以實現(xiàn) 一鍵復制 的功能進行輔助
  • 另一種就直接提示只支持 PC 端下載,放棄對移動端的下載操作

最后

綜上所述,實際在實現(xiàn) pdf 預覽的過程中可能暫時沒有辦法達到完美的方式,特別是針對類似 發(fā)票類pdf 文件,仍存在如下的問題:

  • 無法保證 h5 移動端都具備 下載 功能
  • 無法保證 pdf 預覽 時,預覽的字體和實際發(fā)票 字體 保持一致

現(xiàn)有大部分的預覽方式都基于 pdf.js 的方式實現(xiàn),而 pdf.js 內(nèi)部通過 PDFJs.getDocument(url/buffer) 的方式基于 文件地址數(shù)據(jù)流 來獲取內(nèi)容,再通過 canvas 處理渲染 pdf 文件,感興趣可以去研究 pdf.js 源碼。

pdf.js 帶來相關(guān)問題就是如果對應的 pdf 文件中包含了 pdf.js 中不存在的字體,那么就無法完整渲染,另外渲染出來的字體和原本的 pdf 文件字體會存在差異。

針對這兩點,目前發(fā)現(xiàn)谷歌內(nèi)置的 pdf 插件似乎提供了很好的支持,意味著其他瀏覽器如果包含了谷歌相關(guān)的插件(如:Edge、QQ Browser),就可以直接基于 <iframe> 的方式實現(xiàn)預覽,又或者為了更嚴謹字體一致性只能通過下載的方式來查看源文件。

實現(xiàn)不了產(chǎn)品的要求怎么辦?

例如上述探討的方案其實無法滿足文章開頭提到的部分要求。產(chǎn)品提出需求的目的也是為了提供更好的用戶體驗(`正常情況下`),但是這些要求仍然要落實到技術(shù)上,而技術(shù)支持程度如何需要我們及時反饋(`除非你的產(chǎn)品是技術(shù)經(jīng)驗`),因此作為開發(fā)者你需要提供充足的內(nèi)容向產(chǎn)品證明,然后自己再給出一些間接實現(xiàn)的方案(又或者產(chǎn)品自己就給出新的方案),看是否符合 第二預期,核心就是 合理溝通 + 其他方案每個人的處境不同,實際情況也許 ... 懂得都懂)。

js pdf文件下載,微信小程序,javascript,pdf,前端

以上是個人的一些看法和理解,有不當之處,可以在評論區(qū)指正?。?!

希望本文對你有所幫助?。?!文章來源地址http://www.zghlxwxcb.cn/news/detail-606066.html

到了這里,關(guān)于js實現(xiàn)PDF 預覽和文件下載的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關(guān)文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包