Skip to content

用 Vue 3 与 pdf-lib 在浏览器中整理 PDF 页面 ​

译注:本文编译自 dev.to 上的一篇技术教程,原文标题为《How to Organize PDF Pages in the Browser with Vue 3 and pdf-lib》,作者演示了如何完全在浏览器端实现 PDF 页面整理功能。原文链接见文末。

重新组织 PDF 页面,通常意味着要调整页面顺序、插入空白页,以及管理页面子集。本文介绍如何用 Vue 3 和 pdf-lib 构建一个基于浏览器的 PDF 整理工具。

要解决的问题:灵活的页面管理 ​

PDF 整理功能大致包含以下几个环节:

  1. 加载源 PDF 文件
  2. 将页面以可拖拽的缩略图形式展示
  3. 支持重排、删除与插入
  4. 按修改后的页面顺序生成新的 PDF

技术栈 ​

  • Vue 3,使用 Composition API
  • pdf-lib,负责 PDF 操作
  • SortableJS,负责拖拽排序
  • Vite,负责打包

核心实现 ​

vue
<script setup lang="ts">
import { ref } from 'vue'
import { PDFDocument } from 'pdf-lib'
import Sortable from 'sortablejs'

const file = ref<File | null>(null)
const pages = ref<number[]>([])
const organizing = ref(false)
const result = ref<Uint8Array | null>(null)

async function handleFile(e: Event) {
  const input = e.target as HTMLInputElement
  if (!input.files?.[0]) return
  file.value = input.files[0]

  const arrayBuffer = await file.value.arrayBuffer()
  const pdfDoc = await PDFDocument.load(arrayBuffer)
  pages.value = Array.from({ length: pdfDoc.getPageCount() }, (_, i) => i)
}

function initDragDrop() {
  const el = document.getElementById('page-list')
  if (!el) return

  Sortable.create(el, {
    animation: 150,
    onEnd: (evt) => {
      const oldIndex = evt.oldIndex!
      const newIndex = evt.newIndex!
      const [moved] = pages.value.splice(oldIndex, 1)
      pages.value.splice(newIndex, 0, moved)
    }
  })
}

async function organizePdf() {
  if (!file.value || pages.value.length === 0) return
  organizing.value = true

  const arrayBuffer = await file.value.arrayBuffer()
  const pdfDoc = await PDFDocument.load(arrayBuffer)
  const newPdf = await PDFDocument.create()

  for (const pageIndex of pages.value) {
    const [copiedPage] = await newPdf.copyPages(pdfDoc, [pageIndex])
    newPdf.addPage(copiedPage)
  }

  result.value = await newPdf.save()
  organizing.value = false
}
</script>

关键实现细节 ​

1. 页面索引追踪 ​

维护一个页面索引数组来表示当前顺序:

typescript
const pages = ref<number[]>([])
// 初始状态:[0, 1, 2, 3, ...]

2. 拖拽重排 ​

用 SortableJS 处理视觉上的重排,并同步更新索引数组:

typescript
Sortable.create(el, {
  onEnd: (evt) => {
    const [moved] = pages.value.splice(evt.oldIndex, 1)
    pages.value.splice(evt.newIndex, 0, moved)
  }
})

3. 生成整理后的 PDF ​

按新的顺序复制页面:

typescript
for (const pageIndex of pages.value) {
  const [copiedPage] = await newPdf.copyPages(pdfDoc, [pageIndex])
  newPdf.addPage(copiedPage)
}

4. 插入空白页 ​

在指定位置插入空白页:

typescript
pages.value.splice(insertIndex, 0, -1) // -1 表示空白页

导出时再作相应处理:

typescript
if (pageIndex === -1) {
  newPdf.addPage() // 添加空白页
} else {
  const [copiedPage] = await newPdf.copyPages(pdfDoc, [pageIndex])
  newPdf.addPage(copiedPage)
}

局限性 ​

没有可视化页面预览 ​

为所有页面生成缩略图预览可能非常占用内存。

解决方案:懒加载生成缩略图,或只渲染前 N 页。

一次只能处理一个文件 ​

每次会话只能整理一个 PDF。

解决方案:增加多文件上传支持。

导出后无法撤销 ​

文件一旦下载,改动就无法回退。

解决方案:为近期操作增加撤销栈。

小结 ​

构建一个浏览器端 PDF 整理工具,主要涉及:

  1. 用 pdf-lib 加载并解析 PDF
  2. 实现拖拽重排
  3. 按目标顺序复制页面
  4. 支持插入空白页

作者提供了一个在线示例,可在 en.sotool.top/organize-pdf 试用。

原文链接 ​

https://dev.to/sunshey/how-to-organize-pdf-pages-in-the-browser-with-vue-3-and-pdf-lib-lmo