Vue3實(shí)現(xiàn)PDF文件解析與預(yù)覽的完整實(shí)踐
前言
在實(shí)際的前端開發(fā)中,經(jīng)常會碰到需要在線預(yù)覽PDF文件的場景,比如后臺管理系統(tǒng)查看合同、教育平臺展示試卷、審批系統(tǒng)預(yù)覽發(fā)票等等。這類需求的核心目標(biāo)就是:用戶不需要下載,就能直接在頁面中查看 PDF 文檔內(nèi)容。
最近,我在公司的Vue3項(xiàng)目中剛好負(fù)責(zé)了一塊PDF預(yù)覽功能的開發(fā),趁熱打鐵把這塊功能的實(shí)現(xiàn)過程整理出來,希望能幫到有類似需求的朋友。
常見的 PDF 文件預(yù)覽方式有哪些?
在查閱了一些資料,也踩了幾個(gè)坑之后,我發(fā)現(xiàn)目前主流的三種方式大概如下:
| 實(shí)現(xiàn)方式 | 簡介 | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|---|---|
| iframe / embed 標(biāo)簽 | 直接嵌入瀏覽器內(nèi)核的渲染功能 | 簡單,不需要引入額外依賴 | 樣式和交互無法自定義,兼容性差 |
| PDF.js(pdfjs-dist) | 由 Mozilla 出品的開源 PDF 渲染器,支持 canvas / svg 渲染 | 功能強(qiáng)大,完全前端渲染,可高度定制 | 實(shí)現(xiàn)略復(fù)雜,需要自己處理分頁、縮放等邏輯 |
| 后端轉(zhuǎn)圖片 | 將 PDF 轉(zhuǎn)成圖片給前端展示 | 前端負(fù)擔(dān)小,兼容性好 | 沒有文字層,不能選中、搜索、復(fù)制文字,服務(wù)端壓力大 |
我最終選用的是 PDF.js(pdfjs-dist),原因很簡單:其由 Mozilla 維護(hù),穩(wěn)定性強(qiáng),功能完善,支持多頁分頁、縮放、搜索、高亮等能力。還有就是需求需要的是可以交互、可以選中文本的預(yù)覽組件,而且盡量不依賴后端處理。
開始動(dòng)手:安裝并配置 pdfjs-dist
先安裝它:
npm install pdfjs-dist
然后,在我們自己的工具函數(shù)里,引入并配置好Web Worker路徑(為了性能,PDF.js會用 worker異步解析):
import * as pdfjsLib from 'pdfjs-dist/build/pdf.mjs'
pdfjsLib.GlobalWorkerOptions.workerSrc = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${(pdfjsLib as any).version}/build/pdf.worker.mjs`
注意:如果你用的是Vite,pdfjs的worker不好自動(dòng)打包,用CDN路徑是目前最穩(wěn)妥的方式。當(dāng)然你也可以自行下載后配置本地路徑。
核心功能封裝:純 JS 函數(shù)渲染PDF到DOM中
以下是我封裝好的一個(gè)核心函數(shù),接收PDF文件地址和容器DOM,然后渲染出完整PDF頁內(nèi)容。
// utils/pdfRenderer.ts
import * as pdfjsLib from 'pdfjs-dist/build/pdf.mjs'
pdfjsLib.GlobalWorkerOptions.workerSrc = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${(pdfjsLib as any).version}/build/pdf.worker.mjs`
interface RenderPdfOptions {
scale?: number
page?: number
}
export async function renderPdfToContainer(
container: HTMLElement,
url: string,
options?: RenderPdfOptions
) {
container.innerHTML = ''
const scale = options?.scale ?? 1.5
const targetPage = options?.page
try {
const loadingTask = pdfjsLib.getDocument(url)
const pdf = await loadingTask.promise
const pages = targetPage
? [targetPage]
: Array.from({ length: pdf.numPages }, (_, i) => i + 1)
for (const pageNum of pages) {
const page = await pdf.getPage(pageNum)
const viewport = page.getViewport({ scale })
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
if (!context) continue
canvas.width = viewport.width
canvas.height = viewport.height
container.appendChild(canvas)
const renderContext = {
canvasContext: context,
viewport
}
await page.render(renderContext).promise
}
} catch (error) {
console.error('PDF 加載失敗:', error)
container.innerHTML = '<p style="color:red">加載失敗,請檢查文件格式或地址是否正確。</p>'
}
}
在 Vue3 組件中如何使用?
在Vue3項(xiàng)目中寫了一個(gè)簡單組件,效果就是加載PDF文件并將其渲染到頁面中:
<template>
<div ref="pdfWrapper" class="pdf-container" />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { renderPdfToContainer } from '@/utils/pdfRenderer'
const pdfWrapper = ref<HTMLElement>()
const pdfUrl = '/files/sample.pdf' // 替換成你的文件地址
onMounted(() => {
if (pdfWrapper.value) {
renderPdfToContainer(pdfWrapper.value, pdfUrl, { scale: 1.25 })
}
})
</script>
<style scoped>
.pdf-container {
width: 100%;
overflow: auto;
background: #f6f6f6;
padding: 16px;
}
</style>
小提醒:為了避免渲染異常,renderPdfToContainer 必須在 onMounted 后 DOM 掛載后調(diào)用。
遇到的問題和一些解決方案
這里是開發(fā)過程中真實(shí)踩過的幾個(gè)坑,順手整理一下,希望你少走彎路。
問題一:加載失敗,控制臺報(bào)錯(cuò) “Unexpected token '<'…”
原因:PDF地址可能不對,返回的不是PDF,而是HTML錯(cuò)誤頁。 解決方式:
- 檢查地址是不是
404 - 用
fetch(url)看看實(shí)際返回內(nèi)容是不是PDF
問題二:PDF渲染模糊,不清晰
原因:canvas分辨率低或者devicePixelRatio沒處理。 優(yōu)化方法:
const dpr = window.devicePixelRatio || 1
canvas.width = viewport.width * dpr
canvas.height = viewport.height * dpr
canvas.style.width = `${viewport.width}px`
canvas.style.height = `${viewport.height}px`
context.setTransform(dpr, 0, 0, dpr, 0, 0)
這樣處理后,高清屏下也能展示得更清晰。
問題三:只想加載某一頁怎么辦?
直接傳 page 參數(shù):
renderPdfToContainer(dom, url, { page: 1 })
我自己在做分頁加載的時(shí)候也用到了這個(gè)邏輯。
問題四:怎么支持本地文件預(yù)覽(用戶上傳后立即預(yù)覽)?
const reader = new FileReader()
reader.onload = async () => {
const typedArray = new Uint8Array(reader.result as ArrayBuffer)
const pdf = await pdfjsLib.getDocument({ data: typedArray }).promise
// 同樣調(diào)用 page.render 即可渲染
}
reader.readAsArrayBuffer(file)
這個(gè)寫法可以用在 <input type="file"> 的上傳場景。
小結(jié)一下
整個(gè)PDF文件解析功能,其實(shí)可以拆成三步:
- 引入
pdfjs-dist并設(shè)置好worker - 封裝一個(gè)
renderPdfToContainer函數(shù)做PDF渲染 - 在
Vue3項(xiàng)目中按需調(diào)用
整個(gè)過程其實(shí)不復(fù)雜,但很多資料都散著,或者講得太官方,不太實(shí)用。我這次是花了些時(shí)間整理,并親自踩坑測試的,如果你也有這類需求,希望這篇文章能讓你少走幾步彎路。
結(jié)語
Vue3項(xiàng)目中實(shí)現(xiàn)PDF預(yù)覽并不難,但要實(shí)現(xiàn) 高性能、強(qiáng)交互、低資源占用 的用戶體驗(yàn),需要我們不斷抽象渲染邏輯、優(yōu)化性能瓶頸并兼顧跨平臺適配。希望本文能幫助你快速掌握在Vue3中集成PDF.js的實(shí)戰(zhàn)方案。
以上就是Vue3實(shí)現(xiàn)PDF文件解析與預(yù)覽的完整實(shí)踐的詳細(xì)內(nèi)容,更多關(guān)于Vue3 PDF解析與預(yù)覽的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue a標(biāo)簽點(diǎn)擊實(shí)現(xiàn)賦值方式
這篇文章主要介紹了vue a標(biāo)簽點(diǎn)擊實(shí)現(xiàn)賦值方式,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
vue中動(dòng)態(tài)select的使用方法示例
這篇文章主要介紹了vue中動(dòng)態(tài)select的使用方法,結(jié)合實(shí)例形式分析了vue.js使用動(dòng)態(tài)select創(chuàng)建下拉菜單相關(guān)實(shí)現(xiàn)技巧與操作注意事項(xiàng),需要的朋友可以參考下2019-10-10
vue使用swiper實(shí)現(xiàn)中間大兩邊小的輪播圖效果
這篇文章主要介紹了vue使用swiper實(shí)現(xiàn)中間大兩邊小的輪播圖效果,本文分步驟通過實(shí)例代碼講解的非常詳細(xì),需要的朋友可以參考下2019-11-11
vue3點(diǎn)擊出現(xiàn)彈窗后背景變暗且不可操作的實(shí)現(xiàn)代碼
這篇文章主要介紹了vue3點(diǎn)擊出現(xiàn)彈窗后背景變暗且不可操作的實(shí)現(xiàn)代碼,本文通過實(shí)例代碼圖文相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08
vue中watch的實(shí)際開發(fā)學(xué)習(xí)筆記
watch是Vue實(shí)例的一個(gè)屬性是用來響應(yīng)數(shù)據(jù)的變化,需要在數(shù)據(jù)變化時(shí)執(zhí)行異步或開銷較大的操作時(shí),這個(gè)方式是最有用的,下面這篇文章主要給大家介紹了關(guān)于vue中watch的實(shí)際開發(fā)筆記,需要的朋友可以參考下2022-11-11

