最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue中使用PDF.js的實(shí)戰(zhàn)指南

 更新時(shí)間:2026年03月02日 10:02:40   作者:夏默Notes  
本文主要介紹了 Vue中使用PDF.js的實(shí)戰(zhàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

環(huán)境準(zhǔn)備

1. 安裝依賴(lài)

npm install pdfjs-dist

2. 配置 Worker

PDF.js 需要 Worker 來(lái)處理 PDF 解析。推薦使用本地 Worker 文件:

步驟 1:復(fù)制 Worker 文件到 public 目錄

# 從 node_modules 復(fù)制 worker 文件到 public 目錄
copy node_modules\pdfjs-dist\build\pdf.worker.min.mjs public\pdf.worker.min.mjs

步驟 2:在代碼中配置 Worker

import * as pdfjsLib from 'pdfjs-dist'

// 方式1: 使用本地 worker 文件(推薦,穩(wěn)定可靠)
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.mjs'

// 方式2: 使用 CDN(備選方案,需要網(wǎng)絡(luò)連接)
// pdfjsLib.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjsLib.version}/pdf.worker.min.js`

3. Vite 配置(可選)

如果需要支持 ES2015 打包場(chǎng)景,在 vite.config.ts 中添加:

export default defineConfig({
  // ... 其他配置
  build: {
    target: 'es2015',
    rollupOptions: {
      output: {
        format: 'es'
      }
    }
  },
  optimizeDeps: {
    include: ['pdfjs-dist', 'pdfjs-dist/web/pdf_viewer.mjs']
  }
})

方式一:?jiǎn)雾?yè)查看器(基礎(chǔ)版)

特點(diǎn)

  • ? 單頁(yè)顯示,分頁(yè)導(dǎo)航
  • ? 支持縮放功能
  • ? 代碼簡(jiǎn)單,易于理解
  • ? 不支持文本選擇和復(fù)制
  • ? 一次只顯示一頁(yè)

核心代碼

<template>
  <div class="pdf-viewer-container">
    <!-- 文件上傳 -->
    <el-upload :on-change="handleFileChange">
      <el-button>選擇 PDF 文件</el-button>
    </el-upload>
    <!-- 控制按鈕 -->
    <div v-if="pdfDoc">
      <el-button @click="previousPage">上一頁(yè)</el-button>
      <el-button>{{ currentPage }} / {{ totalPages }}</el-button>
      <el-button @click="nextPage">下一頁(yè)</el-button>
      <el-button @click="zoomIn">放大</el-button>
      <el-button @click="zoomOut">縮小</el-button>
    </div>
    <!-- Canvas 渲染 -->
    <div class="pdf-viewer">
      <canvas ref="pdfCanvas"></canvas>
    </div>
  </div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import * as pdfjsLib from 'pdfjs-dist'

// 設(shè)置 worker
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.mjs'

// 響應(yīng)式數(shù)據(jù)
const pdfDoc = ref<any>(null)
const currentPage = ref(1)
const totalPages = ref(0)
const scale = ref(1.5)
const pdfCanvas = ref<HTMLCanvasElement | null>(null)

// 加載 PDF
const loadPdf = async (data: Uint8Array) => {
  const loadingTask = pdfjsLib.getDocument({ data })
  pdfDoc.value = await loadingTask.promise
  totalPages.value = pdfDoc.value.numPages
  await renderPage(1)
}

// 渲染頁(yè)面
const renderPage = async (pageNum: number) => {
  const page = await pdfDoc.value.getPage(pageNum)
  const viewport = page.getViewport({ scale: scale.value })
  
  const canvas = pdfCanvas.value
  if (!canvas) return
  
  canvas.height = viewport.height
  canvas.width = viewport.width
  
  const context = canvas.getContext('2d')
  await page.render({
    canvasContext: context,
    viewport: viewport
  }).promise
}
</script>

演示

適用場(chǎng)景

  • 簡(jiǎn)單的 PDF 預(yù)覽需求
  • 只需要單頁(yè)查看
  • 不需要文本選擇功能

方式二:多頁(yè)查看器(性能優(yōu)化版)

特點(diǎn)

  • ? 顯示所有頁(yè)面,垂直滾動(dòng)
  • ? 懶加載優(yōu)化(只渲染可見(jiàn)頁(yè)面)
  • ? 支持文本選擇和復(fù)制
  • ? 性能優(yōu)化,適合大文檔
  • ? 使用 Intersection Observer 實(shí)現(xiàn)懶加載

核心代碼

<template>
  <div class="pdf-viewer-container">
    <!-- 文件上傳 -->
    <el-upload :on-change="handleFileChange">
      <el-button>選擇 PDF 文件</el-button>
    </el-upload>
    <!-- 所有頁(yè)面容器 -->
    <div class="pdf-viewer" ref="pdfViewer">
      <div v-for="pageNum in totalPages" :key="pageNum" :data-page="pageNum">
        <div class="page-header">第 {{ pageNum }} 頁(yè)</div>
        <div class="page-content">
          <canvas class="page-canvas"></canvas>
          <div class="text-layer"></div>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import * as pdfjsLib from 'pdfjs-dist'

// 懶加載相關(guān)
const renderingPages = ref<Set<number>>(new Set())
const renderedPages = ref<Set<number>>(new Set())
let intersectionObserver: IntersectionObserver | null = null

// 初始化懶加載
const initLazyLoading = async () => {
  intersectionObserver = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          const pageNum = parseInt(entry.target.getAttribute('data-page') || '0')
          if (pageNum > 0 && !renderedPages.value.has(pageNum)) {
            renderPage(pageNum)
          }
        }
      })
    },
    {
      rootMargin: '200px 0px', // 提前 200px 開(kāi)始加載
      threshold: 0.01
    }
  )

  // 觀(guān)察所有頁(yè)面容器
  for (let pageNum = 1; pageNum <= totalPages.value; pageNum++) {
    const pageWrapper = pdfViewer.value?.querySelector(`[data-page="${pageNum}"]`)
    if (pageWrapper) {
      intersectionObserver.observe(pageWrapper)
    }
  }

  // 立即渲染前幾頁(yè)
  for (let pageNum = 1; pageNum <= Math.min(3, totalPages.value); pageNum++) {
    renderPage(pageNum)
  }
}

// 渲染單個(gè)頁(yè)面(包含文本層)
const renderPage = async (pageNum: number) => {
  if (renderingPages.value.has(pageNum) || renderedPages.value.has(pageNum)) {
    return
  }

  renderingPages.value.add(pageNum)

  const pageWrapper = pdfViewer.value?.querySelector(`[data-page="${pageNum}"]`)
  const canvas = pageWrapper?.querySelector('canvas') as HTMLCanvasElement
  const textLayer = pageWrapper?.querySelector('.text-layer') as HTMLElement

  const page = await pdfDoc.value.getPage(pageNum)
  const viewport = page.getViewport({ scale: scale.value })

  // 渲染 Canvas
  canvas.height = viewport.height
  canvas.width = viewport.width
  const context = canvas.getContext('2d')
  await page.render({
    canvasContext: context,
    viewport: viewport
  }).promise

  // 渲染文本層(用于文字選擇和復(fù)制)
  const textContent = await page.getTextContent()
  // ... 文本層渲染邏輯

  renderingPages.value.delete(pageNum)
  renderedPages.value.add(pageNum)
}
</script>

性能優(yōu)化要點(diǎn)

  1. 懶加載:使用 Intersection Observer 只渲染可見(jiàn)頁(yè)面
  2. 分批渲染:縮放時(shí)按批次重新渲染,避免阻塞 UI
  3. 狀態(tài)管理:跟蹤已渲染頁(yè)面,避免重復(fù)渲染

演示

適用場(chǎng)景

  • 需要查看所有頁(yè)面
  • 大文檔(100+ 頁(yè))
  • 需要文本選擇和復(fù)制
  • 需要性能優(yōu)化

方式三:官方組件化查看器(推薦)

特點(diǎn)

  • ? 使用 PDF.js 官方組件(PDFViewer、PDFLinkService)
  • ? 自動(dòng)文本層和注釋層渲染
  • ? 完整的事件系統(tǒng)
  • ? 官方維護(hù),穩(wěn)定可靠
  • ? 可自定義樣式和行為
  • ? 這是 PDF.js 官方推薦的生產(chǎn)環(huán)境使用方式

核心代碼

<template>
  <div class="pdf-viewer-container">
    <div class="pdf-controls">
      <el-upload :on-change="handleFileChange">
        <el-button>選擇 PDF 文件</el-button>
      </el-upload>
    </div>
    <div class="pdf-viewer-wrapper">
      <div ref="pdfViewerContainer" class="pdf-viewer-container-inner"></div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import * as pdfjsLib from 'pdfjs-dist'
import { PDFViewer, EventBus, PDFLinkService } from 'pdfjs-dist/web/pdf_viewer.mjs'
import 'pdfjs-dist/web/pdf_viewer.css'

// 設(shè)置 worker
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.mjs'

let pdfViewer: PDFViewer | null = null
let eventBus: EventBus | null = null
const pdfViewerContainer = ref<HTMLElement | null>(null)

// 初始化 PDF 查看器
const initPDFViewer = async (pdfDocument: any) => {
  const container = pdfViewerContainer.value
  
  // 創(chuàng)建滾動(dòng)容器(必須是絕對(duì)定位)
  const scrollContainer = document.createElement('div')
  scrollContainer.className = 'pdf-viewer-scroll'
  scrollContainer.style.position = 'absolute'
  scrollContainer.style.top = '0'
  scrollContainer.style.left = '0'
  scrollContainer.style.width = '100%'
  scrollContainer.style.height = '100%'
  scrollContainer.style.overflow = 'auto'
  
  // 創(chuàng)建查看器容器
  const viewer = document.createElement('div')
  viewer.className = 'pdfViewer'
  
  scrollContainer.appendChild(viewer)
  container.appendChild(scrollContainer)

  // 創(chuàng)建事件總線(xiàn)和鏈接服務(wù)
  eventBus = new EventBus()
  const linkService = new PDFLinkService({
    eventBus: eventBus,
    externalLinkTarget: 2
  })

  // 創(chuàng)建 PDFViewer 實(shí)例
  pdfViewer = new PDFViewer({
    container: scrollContainer,  // 外層滾動(dòng)容器(必須絕對(duì)定位)
    viewer: viewer,              // 內(nèi)層查看器容器
    eventBus: eventBus,
    linkService: linkService
  })

  // 設(shè)置 PDF 文檔
  pdfViewer.setDocument(pdfDocument)

  // 監(jiān)聽(tīng)事件
  eventBus.on('pagesinit', () => {
    totalPages.value = pdfViewer?.pagesCount || 0
  })

  eventBus.on('scalechanging', (evt: any) => {
    currentScale.value = evt.scale
  })
}

// 清理
onUnmounted(() => {
  if (pdfViewer) {
    pdfViewer.cleanup()
  }
})
</script>

重要配置

  1. Container 必須是絕對(duì)定位
scrollContainer.style.position = 'absolute'
  1. 需要兩個(gè)容器
  • container: 外層滾動(dòng)容器(絕對(duì)定位)
  • viewer: 內(nèi)層查看器容器(.pdfViewer
  1. 導(dǎo)入樣式
import 'pdfjs-dist/web/pdf_viewer.css'

演示

適用場(chǎng)景

  • ? 生產(chǎn)環(huán)境(最推薦)
  • 需要完整功能(文本選擇、注釋、鏈接)
  • 需要官方維護(hù)和更新
  • 需要自定義樣式和行為

方式四:iframe 嵌入官方 HTML

特點(diǎn)

  • ? 直接使用官方 HTML 文件
  • ? 功能完整,無(wú)需額外開(kāi)發(fā)
  • ? CDN 版本無(wú)法訪(fǎng)問(wèn) Blob URL(跨域限制)
  • ? 樣式隔離,難以自定義
  • ? 無(wú)法深度集成到 Vue 應(yīng)用
  • ?? 不推薦用于生產(chǎn)環(huán)境

核心代碼

<template>
  <div class="pdf-viewer-container">
    <el-upload :on-change="handleFileChange">
      <el-button>選擇 PDF 文件</el-button>
    </el-upload>
    <div class="pdf-viewer-wrapper">
      <iframe
        v-if="viewerUrl"
        :src="viewerUrl"
        class="pdf-viewer-iframe"
        frameborder="0"
      ></iframe>
    </div>
  </div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import * as pdfjsLib from 'pdfjs-dist'

// 設(shè)置 worker(注意:路徑已更新)
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdfjs-dist/build/pdf.worker.min.mjs'

const pdfUrl = ref<string>('')
const viewerUrl = ref<string>('')

// 檢查本地查看器是否存在
const checkLocalViewer = async (): Promise<boolean> => {
  try {
    const response = await fetch('/pdfjs-dist/web/viewer.html', { method: 'HEAD' })
    return response.ok
  } catch {
    return false
  }
}

// 加載 PDF
const loadPdf = async (file: File) => {
  // 清理舊的 URL
  if (pdfUrl.value) {
    URL.revokeObjectURL(pdfUrl.value)
  }

  // 創(chuàng)建 Blob URL
  const blob = new Blob([file], { type: 'application/pdf' })
  const url = URL.createObjectURL(blob)
  pdfUrl.value = url

  // 檢查本地查看器是否存在
  const hasLocalViewer = await checkLocalViewer()
  
  if (!hasLocalViewer) {
    // 本地查看器不存在,提示用戶(hù)配置
    console.error('本地查看器未找到,請(qǐng)按照以下步驟配置:')
    console.error('1. 訪(fǎng)問(wèn) https://github.com/mozilla/pdf.js/releases 下載最新版本')
    console.error('2. 解壓后將 web 目錄復(fù)制到 public/pdfjs-dist/web/')
    console.error('3. 確保 viewer.html 的路徑是 /pdfjs-dist/web/viewer.html')
    return
  }

  // ? 必須使用本地查看器(避免跨域問(wèn)題)
  // ?? CDN 版本無(wú)法訪(fǎng)問(wèn) Blob URL,會(huì)導(dǎo)致跨域錯(cuò)誤
  const viewerPath = '/pdfjs-dist/web/viewer.html'
  const encodedUrl = encodeURIComponent(url)
  viewerUrl.value = `${viewerPath}?file=${encodedUrl}`
}

// 清理資源
onUnmounted(() => {
  if (pdfUrl.value) {
    URL.revokeObjectURL(pdfUrl.value)
  }
})
</script>

使用本地查看器

?? 重要:必須使用本地查看器,CDN 版本無(wú)法訪(fǎng)問(wèn) Blob URL(跨域限制)

  1. 下載 PDF.js 官方查看器:
  1. 復(fù)制到項(xiàng)目:
# 將 web 目錄復(fù)制到 public/pdfjs-dist/web/
# 最終結(jié)構(gòu)應(yīng)該是:
public/
  └── pdfjs-dist/
      ├── build/
      │   └── pdf.worker.min.mjs
      └── web/
          ├── viewer.html
          ├── viewer.js
          ├── viewer.css
          └── ... (其他文件)
  1. 驗(yàn)證配置:
  • 啟動(dòng)開(kāi)發(fā)服務(wù)器后,訪(fǎng)問(wèn) http://localhost:xxxx/pdfjs-dist/web/viewer.html 應(yīng)該能看到 PDF.js 官方查看器界面
  • 代碼會(huì)自動(dòng)檢測(cè)本地查看器是否存在,如果不存在會(huì)提示配置步驟

演示

適用場(chǎng)景

  • 快速原型
  • 演示和測(cè)試
  • 不需要深度集成
  • ?? 不推薦用于生產(chǎn)環(huán)境

最佳實(shí)踐

1. Worker 配置

推薦:使用本地 Worker 文件

// ? 推薦方式1: 放在 public 根目錄
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.mjs'

// ? 推薦方式2: 放在 public/pdfjs-dist/build/ 目錄(與官方查看器結(jié)構(gòu)一致)
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdfjs-dist/build/pdf.worker.min.mjs'

// ? 不推薦(可能有跨域問(wèn)題)
pdfjsLib.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/...`

注意:如果使用方式四(iframe),建議將 worker 文件放在 public/pdfjs-dist/build/ 目錄,與官方查看器結(jié)構(gòu)保持一致。

2. 文件驗(yàn)證

始終驗(yàn)證 PDF 文件:

// 驗(yàn)證文件類(lèi)型
if (file.type !== 'application/pdf' && !file.name.endsWith('.pdf')) {
  throw new Error('請(qǐng)選擇 PDF 格式的文件')
}

// 驗(yàn)證 PDF 文件頭
const header = String.fromCharCode(...uint8Array.slice(0, 4))
if (header !== '%PDF') {
  throw new Error('無(wú)效的 PDF 文件格式')
}

3. 錯(cuò)誤處理

提供詳細(xì)的錯(cuò)誤信息:

try {
  await loadPdf(data)
} catch (error: any) {
  let errorMessage = 'PDF 加載失敗'
  if (error?.name === 'InvalidPDFException') {
    errorMessage = '無(wú)效的 PDF 文件結(jié)構(gòu),請(qǐng)確認(rèn)文件是否損壞'
  } else if (error?.name === 'MissingPDFException') {
    errorMessage = 'PDF 文件缺失或無(wú)法訪(fǎng)問(wèn)'
  }
  $message?.error(errorMessage)
}

4. 性能優(yōu)化

  • 懶加載:使用 Intersection Observer 只渲染可見(jiàn)頁(yè)面
  • 分批渲染:縮放時(shí)按批次重新渲染
  • 內(nèi)存管理:及時(shí)清理 Blob URL 和查看器實(shí)例
// 清理 Blob URL
onUnmounted(() => {
  if (pdfUrl.value) {
    URL.revokeObjectURL(pdfUrl.value)
  }
  if (pdfViewer) {
    pdfViewer.cleanup()
  }
})

5. 文本選擇支持

如果需要文本選擇,必須添加文本層:

// 獲取文本內(nèi)容
const textContent = await page.getTextContent()

// 創(chuàng)建文本層元素
textContent.items.forEach((item: any) => {
  const span = document.createElement('span')
  span.textContent = item.str
  span.style.position = 'absolute'
  span.style.color = 'transparent' // 透明,不影響視覺(jué)效果
  span.style.cursor = 'text'
  textLayer.appendChild(span)
})

常見(jiàn)問(wèn)題

Q1: Worker 加載失敗

錯(cuò)誤Setting up fake worker failed

解決方案

  1. 確保 worker 文件在 public 目錄
  2. 使用本地 worker 文件而不是 CDN
  3. 檢查路徑是否正確

Q2: PDF 文件無(wú)法加載

錯(cuò)誤Invalid PDF structure

解決方案

  • 驗(yàn)證文件是否為有效的 PDF 格式
  • 檢查文件是否損壞
  • 添加文件頭驗(yàn)證

Q3: 打包后無(wú)法運(yùn)行

錯(cuò)誤:CORS 錯(cuò)誤

解決方案

  1. 不要直接用 file:// 打開(kāi)打包后的文件
  2. 使用 npm run preview 預(yù)覽
  3. 或使用本地服務(wù)器(http-server、Python 等)

Q4: PDFViewer 初始化失敗

錯(cuò)誤Invalid container and/or viewer option

解決方案

  1. 確保 container 是絕對(duì)定位
  2. 提供 containerviewer 兩個(gè)參數(shù)
  3. 等待 DOM 更新后再初始化

Q5: iframe 方式跨域錯(cuò)誤

錯(cuò)誤SecurityError: Failed to read a named property 'document' from 'Window': Blocked a frame with origin "https://mozilla.github.io" from accessing a cross-origin frame

原因

  • 使用 CDN 版本的官方查看器(https://mozilla.github.io
  • Blob URL 無(wú)法在跨域 iframe 中訪(fǎng)問(wèn)

解決方案

  1. 必須使用本地查看器(推薦)
  • 下載 PDF.js 官方查看器
  • web 目錄復(fù)制到 public/pdfjs-dist/web/
  • 代碼會(huì)自動(dòng)檢測(cè)本地查看器是否存在
  1. 路徑配置
// ? 正確:使用本地查看器
const viewerPath = '/pdfjs-dist/web/viewer.html'

// ? 錯(cuò)誤:CDN 版本無(wú)法訪(fǎng)問(wèn) Blob URL
const viewerPath = 'https://mozilla.github.io/pdf.js/web/viewer.html'
  1. 驗(yàn)證配置
  • 訪(fǎng)問(wèn) http://localhost:3016/pdfjs-dist/web/viewer.html 應(yīng)該能看到查看器界面
  • 如果不存在,代碼會(huì)提示配置步驟

總結(jié)

選擇建議

場(chǎng)景推薦方式
生產(chǎn)環(huán)境方式三(官方組件化)
需要查看所有頁(yè)面方式二(多頁(yè)查看器)
簡(jiǎn)單預(yù)覽方式一(單頁(yè)查看器)
快速原型方式四(iframe)

核心要點(diǎn)

  1. ? Worker 配置:使用本地 worker 文件
  2. ? 文件驗(yàn)證:始終驗(yàn)證 PDF 文件格式
  3. ? 錯(cuò)誤處理:提供詳細(xì)的錯(cuò)誤信息
  4. ? 性能優(yōu)化:使用懶加載和分批渲染
  5. ? 內(nèi)存管理:及時(shí)清理資源
  6. ? 生產(chǎn)環(huán)境:推薦使用官方組件化方式

到此這篇關(guān)于 Vue中使用PDF.js的實(shí)戰(zhàn)指南的文章就介紹到這了,更多相關(guān) Vue使用PDF.js內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

蓬安县| 麻阳| 银川市| 和田县| 乌恰县| 渝北区| 黔西| 惠安县| 邹平县| 平陆县| 襄樊市| 依兰县| 都江堰市| 行唐县| 汽车| 淳安县| 光泽县| 青神县| 宝应县| 阳城县| 玉山县| 潜江市| 兰坪| 三都| 辽源市| 宜城市| 九江市| 曲水县| 巨鹿县| 五原县| 通辽市| 怀化市| 罗江县| 苍南县| 杭州市| 徐闻县| 松潘县| 固安县| 萨迦县| 滨海县| 高邮市|