纯前端 PDF 删除页面实战:Vue 3 + pdf-lib删除指定页面

4 阅读2分钟

最近给 PDF 工具箱加了删除页面功能。这个功能需求很普遍:扫描件有空白页、合并的 PDF 有重复封面、合同只需要保留签字页。这篇文章记录一下纯前端实现方案。

为什么坚持纯前端

PDF 文件经常包含敏感信息:合同、医疗记录、财务报表。即使是"删几页"这种简单操作,很多人也不愿意上传到服务器。

纯前端方案:

  • 文件不离开用户设备
  • 没有后端处理成本
  • 不受服务器文件大小限制
  • 加载完成后可以离线使用

技术栈

  • Vue 3 + Composition API
  • pdf-lib:加载、操作、保存 PDF
npm install pdf-lib

加载 PDF 并获取页数

import { PDFDocument } from 'pdf-lib'const pdfFile = ref<File | null>(null)
const totalPages = ref(0)
​
async function handleFile(files: File[]) {
  if (files.length === 0) return
  pdfFile.value = files[0]
  const bytes = await files[0].arrayBuffer()
  const pdf = await PDFDocument.load(bytes)
  totalPages.value = pdf.getPageCount()
}

两种选页方式

1. 按页码范围

用户输入类似 1, 5-7, 12,解析成页码集合。

function parseDeletePages(input: string, max: number): Set<number> {
  const set = new Set<number>()
  const parts = input.split(',').map(s => s.trim())
​
  for (const part of parts) {
    if (part.includes('-')) {
      const [start, end] = part.split('-').map(Number)
      for (let i = start; i <= end && i <= max; i++) set.add(i)
    } else {
      const n = Number(part)
      if (n >= 1 && n <= max) set.add(n)
    }
  }
​
  return set
}

2. 可视化网格选择

<div class="grid grid-cols-8 gap-2">
  <button
    v-for="n in totalPages"
    :key="n"
    :class="selectedPages.has(n) ? 'bg-red-50 text-red-600' : ''"
    @click="togglePage(n)"
  >
    {{ n }}
  </button>
</div>
const selectedPages = ref<Set<number>>(new Set())
​
function togglePage(n: number) {
  const s = new Set(selectedPages.value)
  if (s.has(n)) s.delete(n)
  else s.add(n)
  selectedPages.value = s
}

删除页面并生成新 PDF

async function deletePages() {
  if (!pdfFile.value) return
​
  const bytes = await pdfFile.value.arrayBuffer()
  const pdf = await PDFDocument.load(bytes)
  const pageCount = pdf.getPageCount()
​
  const toDelete = deleteMode.value === 'range'
    ? parseDeletePages(rangeInput.value, pageCount)
    : selectedPages.value
​
  if (toDelete.size === 0) {
    alert('No pages selected')
    return
  }
​
  if (toDelete.size === pageCount) {
    alert('Cannot delete all pages')
    return
  }
​
  const newPdf = await PDFDocument.create()
  const keepIndices: number[] = []
​
  for (let i = 0; i < pageCount; i++) {
    if (!toDelete.has(i + 1)) keepIndices.push(i)
  }
​
  const pages = await newPdf.copyPages(pdf, keepIndices)
  pages.forEach(p => newPdf.addPage(p))
​
  const blob = new Blob([await newPdf.save()], { type: 'application/pdf' })
  downloadBlob(blob, 'deleted_pages.pdf')
}

下载结果

function downloadBlob(blob: Blob, filename: string) {
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = filename
  a.click()
  URL.revokeObjectURL(url)
}

实际踩坑

  1. UI 用 1-based,内部用 0-based 用户说的"第 5 页"就是 5,但 pdf-lib 数组索引从 0 开始。展示时统一 +1。
  2. 不能删除全部页面 技术上可以生成空 PDF,但用户几乎不需要。要给出明确提示。
  3. 输入要容错 用户会输入 5 - 75—7、多余空格。要清理、分割、截断到有效范围。
  4. 用 copyPages 保留质量 不要重新渲染整个 PDF,只复制保留的页面,内容质量不会损失。

实际效果

  • 支持按范围删除和点选删除
  • 保留剩余页面的文字、图片、链接、元数据
  • 一键下载新 PDF
  • 整个流程不上传服务器

适合场景:扫描件去空白页、合并 PDF 去重复封面、合同只保留签字页等。


工具地址:sotool.top/delete-pages

如果对你有帮助,欢迎点赞收藏,有问题评论区见。