用H5实现PDF预览

实现PDF预览的H5方案

在Web应用中预览PDF文件是常见需求,HTML5提供了多种实现方式。以下是几种主流技术方案及代码示例。

使用iframe嵌入PDF

最简单的方式是通过iframe直接嵌入PDF文件,浏览器会自动调用内置PDF查看器。

<iframe 
  src="/path/to/file.pdf" 
  width="100%" 
  height="600px"
  style="border: none;">
</iframe>

优点:零依赖、实现简单。缺点:样式无法自定义,移动端兼容性较差。

PDF.js方案

Mozilla开源的PDF.js库提供更灵活的PDF渲染方案,支持自定义UI和交互。

安装依赖:

npm install pdfjs-dist

基础实现代码:

// 初始化PDF.js
const pdfjsLib = require('pdfjs-dist');

async function renderPDF(url, container) {
  const loadingTask = pdfjsLib.getDocument(url);
  const pdf = await loadingTask.promise;
  
  // 渲染第一页
  const page = await pdf.getPage(1);
  const viewport = page.getViewport({ scale: 1.0 });
  
  const canvas = document.createElement('canvas');
  const context = canvas.getContext('2d');
  canvas.height = viewport.height;
  canvas.width = viewport.width;
  
  container.appendChild(canvas);
  
  await page.render({
    canvasContext: context,
    viewport: viewport
  }).promise;
}

// 调用示例
renderPDF('document.pdf', document.getElementById('pdf-container'));

自定义PDF查看器

基于PDF.js构建完整查看器:

class PDFViewer {
  constructor(containerId) {
    this.container = document.getElementById(containerId);
    this.currentPage = 1;
    this.pdfDoc = null;
  }

  async loadPDF(url) {
    const loadingTask = pdfjsLib.getDocument(url);
    this.pdfDoc = await loadingTask.promise;
    this.renderPage(this.currentPage);
  }

  async renderPage(num) {
    const page = await this.pdfDoc.getPage(num);
    const viewport = page.getViewport({ scale: 1.5 });
    
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    canvas.height = viewport.height;
    canvas.width = viewport.width;
    
    this.container.innerHTML = '';
    this.container.appendChild(canvas);
    
    await page.render({
      canvasContext: context,
      viewport: viewport
    }).promise;
  }

  nextPage() {
    if(this.currentPage < this.pdfDoc.numPages) {
      this.currentPage++;
      this.renderPage(this.currentPage);
    }
  }

  prevPage() {
    if(this.currentPage > 1) {
      this.currentPage--;
      this.renderPage(this.currentPage);
    }
  }
}

移动端优化方案

针对移动设备需考虑触控交互和性能优化:

// 添加触控事件支持
const viewer = new PDFViewer('pdf-container');
viewer.loadPDF('document.pdf');

let startX = 0;
let endX = 0;

document.getElementById('pdf-container').addEventListener('touchstart', (e) => {
  startX = e.changedTouches[0].screenX;
});

document.getElementById('pdf-container').addEventListener('touchend', (e) => {
  endX = e.changedTouches[0].screenX;
  if(startX - endX > 50) {
    viewer.nextPage();
  } else if(endX - startX > 50) {
    viewer.prevPage();
  }
});

// 实现页面缓存
const pageCache = new Map();

async function getPage(num) {
  if(pageCache.has(num)) {
    return pageCache.get(num);
  }
  const page = await pdfDoc.getPage(num);
  pageCache.set(num, page);
  return page;
}

性能优化技巧

大型PDF文件需要特殊处理:

// 分块加载
async function progressiveRender(pageNum) {
  const page = await pdfDoc.getPage(pageNum);
  const viewport = page.getViewport({ scale: 1.0 });
  
  const canvas = document.createElement('canvas');
  canvas.height = viewport.height;
  canvas.width = viewport.width;
  const ctx = canvas.getContext('2d');
  
  // 先渲染低质量图像
  const renderContext = {
    canvasContext: ctx,
    viewport: viewport,
    intent: 'preview'
  };
  
  await page.render(renderContext).promise;
  
  // 再渲染高质量图像
  renderContext.intent = 'print';
  await page.render(renderContext).promise;
}

// Web Worker处理
const worker = new Worker('pdf.worker.js');
pdfjsLib.GlobalWorkerOptions.workerPort = worker;

实现PDF文本搜索

PDF.js支持文本层渲染和搜索功能:

async function initTextLayer(pageNum) {
  const page = await pdfDoc.getPage(pageNum);
  const textContent = await page.getTextContent();
  const textLayer = new pdfjsLib.TextLayerBuilder({
    textLayerDiv: document.getElementById('text-layer'),
    pageIndex: page.pageIndex,
    viewport: page.getViewport({ scale: 1.0 })
  });
  textLayer.setTextContent(textContent);
  textLayer.render();
}

async function searchText(query) {
  const matches = [];
  for(let i = 1; i <= pdfDoc.numPages; i++) {
    const page = await pdfDoc.getPage(i);
    const textContent = await page.getTextContent();
    textContent.items.forEach(item => {
      if(item.str.includes(query)) {
        matches.push({
          page: i,
          text: item.str
        });
      }
    });
  }
  return matches;
}

完整示例代码

整合上述功能的完整实现:

<!DOCTYPE html>
<html>
<head>
  <title>PDF Viewer</title>
  <style>
    #viewer-container {
      position: relative;
      overflow: auto;
    }
    #pdf-canvas {
      border: 1px solid #ccc;
    }
    #text-layer {
      position: absolute;
      top: 0;
      left: 0;
      pointer-events: none;
    }
    .search-box {
      margin-bottom: 10px;
    }
  </style>
</head>
<body>
  <div class="search-box">
    <input type="text" id="search-input">
    <button id="search-btn">Search</button>
  </div>
  <div id="viewer-container">
    <canvas id="pdf-canvas"></canvas>
    <div id="text-layer"></div>
  </div>
  <script src="pdf.js"></script>
  <script>
    // 完整实现代码...
  </script>
</body>
</html>

以上方案可根据实际需求组合使用,PDF.js方案最灵活但实现复杂度较高,iframe方案最简便但定制性差。在移动端应用中,建议结合Web Worker和分页加载技术优化性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值