DEV Community

sunshey
sunshey

Posted on

How to Organize PDF Pages in the Browser with Vue 3 and pdf-lib

Reorganizing PDF pages requires manipulating page order, inserting blank pages, and managing page subsets.

Here's how to build a browser-based PDF organization tool with Vue 3 and pdf-lib.

The challenge: Flexible page management

PDF organization involves:

  1. Loading the source PDF
  2. Displaying pages as draggable thumbnails
  3. Allowing reordering, deletion, and insertion
  4. Generating a new PDF with the modified page sequence

The stack

  • Vue 3 with Composition API
  • pdf-lib for PDF manipulation
  • SortableJS for drag-and-drop reordering
  • Vite for bundling

The core implementation

<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>
Enter fullscreen mode Exit fullscreen mode

Key implementation details

1. Page index tracking

Maintain an array of page indices that represents the current order:

const pages = ref<number[]>([])
// Initially: [0, 1, 2, 3, ...]
Enter fullscreen mode Exit fullscreen mode

2. Drag-and-drop reordering

Use SortableJS to handle visual reordering and update the index array:

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

3. Creating the organized PDF

Copy pages in the new order:

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

4. Adding blank pages

Insert a blank page at a specific index:

pages.value.splice(insertIndex, 0, -1) // -1 represents blank page
Enter fullscreen mode Exit fullscreen mode

Then handle it during export:

if (pageIndex === -1) {
  newPdf.addPage() // Add blank page
} else {
  const [copiedPage] = await newPdf.copyPages(pdfDoc, [pageIndex])
  newPdf.addPage(copiedPage)
}
Enter fullscreen mode Exit fullscreen mode

Limitations

No visual page preview

Generating thumbnail previews for all pages can be memory-intensive.

Solution: Generate thumbnails lazily or limit to first N pages.

Single file at a time

Can only organize one PDF per session.

Solution: Add support for multiple file uploads.

No undo after export

Once downloaded, changes cannot be undone.

Solution: Add an undo stack for recent operations.

Summary

Building a browser-based PDF organization tool involves:

  1. Using pdf-lib to load and analyze PDFs
  2. Implementing drag-and-drop reordering
  3. Copying pages in the desired order
  4. Supporting blank page insertion

Try it at en.sotool.top/organize.

Top comments (0)