在浏览器里实现 PDF 合并与拆分:pdf-lib 实战指南

0 阅读4分钟

在日常开发中,PDF 处理是一个常见的需求。无论是合并多份合同、提取指定页面,还是在前端直接操作 PDF 文件,我们都希望在浏览器端就能完成,而不是依赖后端服务。

今天介绍一个轻量级的 JavaScript 库 —— pdf-lib,它可以在浏览器和 Node.js 环境中对 PDF 进行各种操作,包括合并、拆分、旋转、填写表单等。


🚀 为什么选择 pdf-lib?

特性说明
纯前端运行无需后端,直接在浏览器中处理 PDF
零依赖不依赖原生插件或外部服务
功能丰富支持合并、拆分、编辑、加密等
TypeScript 支持有完整的类型定义
体积小巧压缩后约 100KB 左右

相比后端方案(如 Python 的 PyPDF2、Java 的 iText),pdf-lib 的优势在于用户体验更流畅——用户选择文件后秒级出结果,无需等待上传下载。


📦 安装与引入

npm 安装

npm install pdf-lib

CDN 引入

<script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js"></script>

基础用法

import { PDFDocument } from 'pdf-lib';
​
// 读取本地 PDF 文件
async function loadPdf(file) {
  const arrayBuffer = await file.arrayBuffer();
  const pdfDoc = await PDFDocument.load(arrayBuffer);
  return pdfDoc;
}

🔗 实战一:PDF 合并

需求:用户上传多份 PDF,按顺序合并成一份文件。

import { PDFDocument } from 'pdf-lib';
​
/**
 * 合并多个 PDF 文件
 * @param {File[]} files - 用户选择的 PDF 文件列表
 * @returns {Promise<Uint8Array>} 合并后的 PDF 数据
 */
async function mergePdfs(files) {
  // 创建一个新的空白 PDF 文档
  const mergedPdf = await PDFDocument.create();
​
  for (const file of files) {
    const arrayBuffer = await file.arrayBuffer();
    const pdf = await PDFDocument.load(arrayBuffer);
    
    // 拷贝所有页面到新文档
    const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
    copiedPages.forEach((page) => mergedPdf.addPage(page));
  }
​
  // 保存为 Uint8Array
  return await mergedPdf.save();
}
​
// 下载合并后的文件
async function downloadMergedPdf(files) {
  const pdfBytes = await mergePdfs(files);
  const blob = new Blob([pdfBytes], { type: 'application/pdf' });
  const url = URL.createObjectURL(blob);
  
  const a = document.createElement('a');
  a.href = url;
  a.download = 'merged.pdf';
  a.click();
  
  URL.revokeObjectURL(url);
}

核心 API 解析

  • PDFDocument.create():创建空白 PDF
  • PDFDocument.load(arrayBuffer):加载已有 PDF
  • copyPages(targetDoc, pageIndices):拷贝页面(注意是异步的)
  • addPage(page):添加页面到文档

✂️ 实战二:PDF 拆分

需求:从大文件中提取指定页码范围,生成新的 PDF。

import { PDFDocument } from 'pdf-lib';
​
/**
 * 提取 PDF 指定页面
 * @param {File} file - 源 PDF 文件
 * @param {number[]} pageIndices - 要提取的页码索引(从0开始)
 * @returns {Promise<Uint8Array>}
 */
async function splitPdf(file, pageIndices) {
  const arrayBuffer = await file.arrayBuffer();
  const srcPdf = await PDFDocument.load(arrayBuffer);
  
  // 创建新文档
  const newPdf = await PDFDocument.create();
  
  // 拷贝指定页面
  const copiedPages = await newPdf.copyPages(srcPdf, pageIndices);
  copiedPages.forEach((page) => newPdf.addPage(page));
  
  return await newPdf.save();
}
​
// 使用示例:提取第 2-5 页(索引 1-4)
async function extractPages(file, startPage, endPage) {
  const indices = [];
  for (let i = startPage - 1; i < endPage; i++) {
    indices.push(i);
  }
  return await splitPdf(file, indices);
}

进阶:按固定页数拆分

/**
 * 将 PDF 按每 N 页拆分成多个文件
 */
async function splitByPageCount(file, pagesPerFile) {
  const arrayBuffer = await file.arrayBuffer();
  const srcPdf = await PDFDocument.load(arrayBuffer);
  const totalPages = srcPdf.getPageCount();
  
  const results = [];
  
  for (let i = 0; i < totalPages; i += pagesPerFile) {
    const end = Math.min(i + pagesPerFile, totalPages);
    const indices = Array.from({ length: end - i }, (_, j) => i + j);
    
    const newPdf = await PDFDocument.create();
    const copiedPages = await newPdf.copyPages(srcPdf, indices);
    copiedPages.forEach((page) => newPdf.addPage(page));
    
    results.push(await newPdf.save());
  }
  
  return results; // 返回多个 Uint8Array
}

🎨 一个简单的 UI 示例

把上面的代码封装成一个简单的网页工具:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>PDF 工具</title>
  <script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js"></script>
</head>
<body>
  <h2>PDF 合并</h2>
  <input type="file" id="mergeInput" multiple accept=".pdf">
  <button onclick="handleMerge()">合并并下载</button>
​
  <script>
    const { PDFDocument } = PDFLib;
​
    async function handleMerge() {
      const files = document.getElementById('mergeInput').files;
      if (files.length < 2) {
        alert('请至少选择两个 PDF 文件');
        return;
      }
      
      const mergedPdf = await PDFDocument.create();
      
      for (const file of files) {
        const bytes = await file.arrayBuffer();
        const pdf = await PDFDocument.load(bytes);
        const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
        pages.forEach(p => mergedPdf.addPage(p));
      }
      
      const pdfBytes = await mergedPdf.save();
      const blob = new Blob([pdfBytes], { type: 'application/pdf' });
      const url = URL.createObjectURL(blob);
      
      const a = document.createElement('a');
      a.href = url;
      a.download = 'merged.pdf';
      a.click();
      URL.revokeObjectURL(url);
    }
  </script>
</body>
</html>

把上面代码保存为 .html 文件,用浏览器打开就能直接用。


⚠️ 注意事项

  1. 大文件性能:pdf-lib 适合处理几十 MB 以内的 PDF,如果是几百 MB 的扫描件,可能会有性能瓶颈
  2. 字体兼容:某些特殊字体嵌入的 PDF,合并后可能出现字体丢失
  3. 加密文件:受密码保护的 PDF 需要先解密才能处理

💡 不想写代码?

如果你只是想快速处理 PDF,不想搭建前端项目、写代码、调试兼容性问题,可以直接用现成的在线工具。

fb7c5a64-969d-4e52-b061-c0c8779c663c.png

image.png 上面提到的合并、拆分功能,还有 PDF 转 Word、压缩等常用操作,这个在线工具集都覆盖了:

  • ✅ 打开网页直接用,不用下载软件
  • ✅ 无需注册登录
  • ✅ 目前完全免费,没有页数限制

工具派:百度搜索:工具派,显示的第一个就是

当然,如果你是开发者,想要把 PDF 功能集成到自己的项目里,pdf-lib 仍然是一个非常值得考虑的选择。


📚 相关链接


有用的话欢迎点赞收藏,有相关的技术问题欢迎在评论区交流 👇