Vue中最简单的PDF引入方法及优缺点分析

在Vue项目中引入PDF文件有多种方法,下面我将介绍最简单实用的几种方案

前面的3种最简单了的,直接一行代码就可以了,并且不依靠依赖,占用为0kb

1=》使用<embed>标签(最简单方法)

只需一行HTML标签,不需要安装任何额外库,

所有现代浏览器都支持,浏览器直接处理PDF渲染

但是呢,有利有弊,万事两难全:

PDF查看器样式直接是固定的,在小屏幕上可能显示不佳,

无法隐藏下载/打印按钮,只能加载同源或CORS允许的PDF(跨域限制)

部分截图:

代码:

javascript 复制代码
<template>
  <div class="pdf-container">
    <h2>使用 &lt;embed&gt; 标签显示PDF</h2>
    <embed 
      src="/docs/sample.pdf" 
      type="application/pdf" 
      width="100%" 
      height="600px"
    />
  </div>
</template>

<style scoped>
.pdf-container {
  max-width: 900px;
  margin: 0 auto;
  padding: 20px;
  background: white;
  border-radius: 10px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}

h2 {
  color: #2c3e50;
  margin-bottom: 20px;
  text-align: center;
}
</style>

2=》使用<object>标签

显示的效果基本上和上面一样的哈

和第一种相比较的话:

优点:

  1. 兼容性更好:支持更老的浏览器

  2. 提供后备内容:当无法显示时显示自定义内容

  3. 相对更标准:符合W3C规范

缺点:

  1. 与embed类似:存在相同的样式和功能限制

  2. 实现稍复杂:需要处理后备内容

部分截图:

代码:

javascript 复制代码
<template>
  <div class="pdf-container">
    <h2>使用 &lt;object&gt; 标签显示PDF</h2>
    <object 
      :data="pdfUrl" 
      type="application/pdf" 
      width="100%" 
      height="600px"
    >
      <p>无法显示PDF文件。请<a :href="pdfUrl">点击这里下载</a>。</p>
    </object>
  </div>
</template>

<script>
export default {
  data() {
    return {
      pdfUrl: "/docs/sample.pdf"
    }
  }
}
</script>

<style scoped>
/* 同上 */
</style>

3=》使用<iframe>标签

只需将PDF文件的URL作为<iframe>src属性即可。

显示的效果基本上和上面两种一样的哈

限制:

  • 如果设置有密码保护,则需要手动输入密码。
  • 预览效果可能不如某些插件。

部分截图:

代码:

javascript 复制代码
<template>
<div class="app-container">
      <div>操作指南</div>
   // 链接:
    <!-- <iframe style="width: 100%; height: 100%;" src="http://xxx.pdf"></iframe> -->
    //本地:
<iframe style="width: 100%; height: 100%;" src="/docs/sample.pdf"></iframe>
</div>
</template>
<script setup>
</script>

在使用 <iframe> 标签加载本地文件时,需要特别注意浏览器安全策略和路径处理问题。

4=》第三方包:

4.1 使用PDF.js

介绍:

pdfjs-dist 是 Mozilla 开发的 PDF.js 库的 NPM 发行版本,用于在浏览器或 Node.js 环境中渲染和处理 PDF 文件。它提供了强大的 API,使开发者能够在网页上实现 PDF 预览、文本提取、表单填写等功能,无需依赖浏览器内置的 PDF 插件。

特性

  1. 纯前端渲染:无需服务器支持,直接在浏览器中解析和渲染 PDF 文件。
  2. 跨平台兼容:支持现代浏览器(Chrome、Firefox、Safari、Edge 等)和 Node.js 环境。
  3. 高性能:使用 Web Workers 实现后台处理,避免阻塞主线程。
  4. 功能丰富
    • 文本提取与搜索
    • 页面渲染与缩放
    • 表单字段支持
    • 注释与标记
    • 书签与大纲解析
  5. 模块化设计:支持按需引入所需功能,减小打包体积。

安装:

javascript 复制代码
#该命令会把 PDF.js 的最新稳定版本安装到项目里:
npm install pdfjs-dist

或者

 # 特定版本 :以 3.4.120 版本为例
npm install pdfjs-dist@3.4.120 

或者

#若仅在开发阶段需要使用 PDF.js,可将其作为开发依赖进行安装:
npm install pdfjs-dist --save-dev

基本用法

1,在浏览器中渲染 PDF
javascript 复制代码
import * as pdfjsLib from 'pdfjs-dist';

// 配置 worker 路径(重要!)
pdfjsLib.GlobalWorkerOptions.workerSrc = 
  `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjsLib.version}/pdf.worker.min.js`;

// 加载并渲染 PDF
const loadingTask = pdfjsLib.getDocument('path/to/your/pdf.pdf');
loadingTask.promise.then(pdf => {
  pdf.getPage(1).then(page => {
    const viewport = page.getViewport({ scale: 1.5 });
    const canvas = document.getElementById('pdf-canvas');
    const context = canvas.getContext('2d');
    canvas.height = viewport.height;
    canvas.width = viewport.width;

    const renderContext = {
      canvasContext: context,
      viewport: viewport
    };
    page.render(renderContext);
  });
});
2,提取 PDF 文本内容
javascript 复制代码
page.getTextContent().then(textContent => {
  const textItems = textContent.items;
  const text = textItems.map(item => item.str).join(' ');
  console.log('提取的文本:', text);
});
3,在 Node.js 中使用
javascript 复制代码
const pdfjsLib = require('pdfjs-dist/legacy/build/pdf.js');
const fs = require('fs');
const path = require('path');

// 读取 PDF 文件(Buffer 格式)
const data = new Uint8Array(fs.readFileSync(path.join(__dirname, 'example.pdf')));

// 解析 PDF
pdfjsLib.getDocument(data).promise.then(pdf => {
  // 处理 PDF...
});

一些需要知道的:

API:

  • getDocument:加载 PDF 文件(支持 URL、ArrayBuffer、Base64 等格式)。
  • pdf.getPage:获取指定页码的页面。
  • page.render:渲染页面到 Canvas。
  • page.getTextContent:提取页面文本内容。
  • pdf.getOutline:获取 PDF 大纲(书签)。
  • pdf.getMetadata:获取 PDF 元数据(标题、作者等)。

注意事项

  1. Worker 配置

    • 必须通过 GlobalWorkerOptions.workerSrc 指定 worker 文件路径,否则渲染会变慢或报错。
    • 可使用 CDN 路径(如示例)或自行打包 worker 文件。
  2. CORS 问题

    • 若从远程 URL 加载 PDF,需确保服务器设置了正确的 CORS 头。
  3. TypeScript 支持

    • 包内已包含类型定义,无需额外安装。
  4. 安全限制

    • 浏览器环境中无法访问本地文件系统,需通过 <input type="file"> 或拖放功能获取文件。

代码示例:

javascript 复制代码
<template>
  <div class="pdf-viewer">
    <div class="header">
      <h1>PDF查看器</h1>
      <div class="controls">
        <button @click="zoomOut" :disabled="scale <= 0.5">-</button>
        <span>{{ Math.round(scale * 100) }}%</span>
        <button @click="zoomIn" :disabled="scale >= 2.0">+</button>
        <button @click="prevPage" :disabled="pageNum <= 1">上一页</button>
        <input v-model.number="pageNum" type="number" min="1" :max="totalPages" @change="goToPage">
        <span>/ {{ totalPages }}</span>
        <button @click="nextPage" :disabled="pageNum >= totalPages">下一页</button>
        <button @click="downloadPDF">下载PDF</button>
      </div>
    </div>
    
    <div class="pdf-container" ref="pdfContainer">
      <canvas ref="pdfCanvas"></canvas>
      <div v-if="loading" class="loader">加载中...</div>
    </div>
  </div>
</template>

<script>
import * as pdfjsLib from 'pdfjs-dist/build/pdf';
import 'pdfjs-dist/build/pdf.worker.entry';

export default {
  data() {
    return {
      pdfUrl: "/docs/sample.pdf",
      pdfDoc: null,
      pageNum: 1,
      totalPages: 0,
      scale: 1.0,
      loading: true
    }
  },
  mounted() {
    this.loadPdf();
  },
  methods: {
    async loadPdf() {
      this.loading = true;
      try {
        pdfjsLib.GlobalWorkerOptions.workerSrc = window.pdfjsWorker;
        
        const loadingTask = pdfjsLib.getDocument(this.pdfUrl);
        this.pdfDoc = await loadingTask.promise;
        this.totalPages = this.pdfDoc.numPages;
        await this.renderPage();
      } catch (err) {
        console.error('PDF加载错误:', err);
        alert('无法加载PDF文件');
      } finally {
        this.loading = false;
      }
    },
    async renderPage() {
      if (!this.pdfDoc) return;
      
      const page = await this.pdfDoc.getPage(this.pageNum);
      const canvas = this.$refs.pdfCanvas;
      const ctx = canvas.getContext('2d');
      const container = this.$refs.pdfContainer;
      
      const viewport = page.getViewport({ scale: this.scale });
      canvas.height = viewport.height;
      canvas.width = viewport.width;
      
      // 居中显示
      canvas.style.margin = '0 auto';
      canvas.style.display = 'block';
      
      const renderContext = {
        canvasContext: ctx,
        viewport: viewport
      };
      
      await page.render(renderContext).promise;
    },
    prevPage() {
      if (this.pageNum > 1) {
        this.pageNum--;
        this.renderPage();
      }
    },
    nextPage() {
      if (this.pageNum < this.totalPages) {
        this.pageNum++;
        this.renderPage();
      }
    },
    goToPage() {
      if (this.pageNum < 1) this.pageNum = 1;
      if (this.pageNum > this.totalPages) this.pageNum = this.totalPages;
      this.renderPage();
    },
    zoomIn() {
      if (this.scale < 2.0) {
        this.scale += 0.1;
        this.renderPage();
      }
    },
    zoomOut() {
      if (this.scale > 0.5) {
        this.scale -= 0.1;
        this.renderPage();
      }
    },
    downloadPDF() {
      const link = document.createElement('a');
      link.href = this.pdfUrl;
      link.download = 'document.pdf';
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    }
  }
}
</script>

<style scoped>
.pdf-viewer {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  max-width: 1000px;
  margin: 20px auto;
  padding: 20px;
  background: #f8f9fa;
  border-radius: 12px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.1);
}

.header {
  background: #2c3e50;
  color: white;
  padding: 15px 20px;
  border-radius: 8px 8px 0 0;
  margin-bottom: 20px;
}

h1 {
  margin: 0 0 15px 0;
  text-align: center;
  font-weight: 500;
}

.controls {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}

button {
  padding: 8px 16px;
  background: #3498db;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.3s;
  font-size: 14px;
}

button:hover {
  background: #2980b9;
}

button:disabled {
  background: #95a5a6;
  cursor: not-allowed;
}

input {
  width: 50px;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
  text-align: center;
}

.pdf-container {
  position: relative;
  min-height: 600px;
  background: white;
  border-radius: 8px;
  overflow: auto;
  box-shadow: 0 2px 10px rgba(0,0,0,0.05);
  border: 1px solid #eee;
}

.loader {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 18px;
  color: #7f8c8d;
}

canvas {
  display: block;
  margin: 0 auto;
  box-shadow: 0 2px 5px rgba(0,0,0,0.05);
}
</style>
相关推荐
midsummer_woo2 小时前
基于springboot的在线教育系统(源码+论文)
vue.js·spring boot·mysql
2301_781668612 小时前
前端基础 JS Vue3 Ajax
前端
上单带刀不带妹3 小时前
前端安全问题怎么解决
前端·安全
Fly-ping3 小时前
【前端】JavaScript 的事件循环 (Event Loop)
开发语言·前端·javascript
SunTecTec3 小时前
IDEA 类上方注释 签名
服务器·前端·intellij-idea
在逃的吗喽4 小时前
黑马头条项目详解
前端·javascript·ajax
袁煦丞4 小时前
有Nextcloud家庭共享不求人:cpolar内网穿透实验室第471个成功挑战
前端·程序员·远程工作
小磊哥er4 小时前
【前端工程化】前端项目开发过程中如何做好通知管理?
前端
拾光拾趣录4 小时前
一次“秒开”变成“转菊花”的线上事故
前端
你我约定有三4 小时前
前端笔记:同源策略、跨域问题
前端·笔记