最近给 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)
}
实际踩坑
- UI 用 1-based,内部用 0-based 用户说的"第 5 页"就是 5,但 pdf-lib 数组索引从 0 开始。展示时统一 +1。
- 不能删除全部页面 技术上可以生成空 PDF,但用户几乎不需要。要给出明确提示。
- 输入要容错 用户会输入
5 - 7、5—7、多余空格。要清理、分割、截断到有效范围。 - 用 copyPages 保留质量 不要重新渲染整个 PDF,只复制保留的页面,内容质量不会损失。
实际效果
- 支持按范围删除和点选删除
- 保留剩余页面的文字、图片、链接、元数据
- 一键下载新 PDF
- 整个流程不上传服务器
适合场景:扫描件去空白页、合并 PDF 去重复封面、合同只保留签字页等。
工具地址:sotool.top/delete-pages
如果对你有帮助,欢迎点赞收藏,有问题评论区见。