DEV Community

sunshey
sunshey

Posted on

How I Built a Blank Page Detector for PDFs with Vue 3 and Canvas

Removing blank pages from a PDF sounds trivial — until you realize that "blank" can mean many things. Is a page with a faint gray background blank? What about a page that has only a header or footer?

Here's how I built a browser-based blank page detector using PDF.js rendering, canvas pixel analysis, and pdf-lib for the actual removal.

Why client-side?

Server-side tools require uploading your file first. For documents with sensitive content, that's a risk. A browser-based approach:

  • Processes everything locally
  • Shows you a preview before deleting
  • Works offline after loading
  • Respects user privacy by design

The stack

  • Vue 3 + Composition API
  • PDF.js (pdfjs-dist) for page rendering
  • html2canvas-style pixel analysis
  • pdf-lib for PDF manipulation
  • Vite for bundling

The core algorithm

<script setup lang="ts">
import { ref } from 'vue'
import * as pdfjs from 'pdfjs-dist'
import { PDFDocument } from 'pdf-lib'

pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.js'

interface BlankPageResult {
  pageIndex: number
  isBlank: boolean
  confidence: number // 0-1, higher means more confident it's blank
}

async function detectBlankPages(
  file: File,
  sensitivity: 'low' | 'medium' | 'high' = 'medium'
): Promise<BlankPageResult[]> {
  const arrayBuffer = await file.arrayBuffer()
  const pdf = await pdfjs.getDocument({ data: arrayBuffer }).promise

  const thresholds = { low: 0.01, medium: 0.05, high: 0.1 } // ratio of non-white pixels
  const results: BlankPageResult[] = []

  for (let i = 1; i <= pdf.numPages; i++) {
    const page = await pdf.getPage(i)
    const viewport = page.getViewport({ scale: 1 })

    // Create offscreen canvas to render and analyze
    const canvas = document.createElement('canvas')
    canvas.width = viewport.width
    canvas.height = viewport.height
    const ctx = canvas.getContext('2d')!

    await page.render({ canvasContext: ctx, viewport }).promise

    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
    const totalPixels = imageData.data.length / 4
    let nonWhitePixels = 0

    // Analyze pixel by pixel
    for (let p = 0; p < imageData.data.length; p += 4) {
      const r = imageData.data[p]
      const g = imageData.data[p + 1]
      const b = imageData.data[p + 2]

      // A pixel is considered "white" if all channels are > 240
      if (r < 240 || g < 240 || b < 240) {
        nonWhitePixels++
      }
    }

    const whiteRatio = nonWhitePixels / totalPixels
    const confidence = Math.min(1, thresholds[sensitivity] * 5)
    const isBlank = whiteRatio < thresholds[sensitivity]

    results.push({
      pageIndex: i - 1, // zero-indexed for pdf-lib
      isBlank,
      confidence: 1 - whiteRatio // higher = more content
    })
  }

  return results
}
</script>
Enter fullscreen mode Exit fullscreen mode

Removing the detected blank pages

Once we know which pages are blank, use pdf-lib to remove them:

async function removeBlankPages(
  arrayBuffer: ArrayBuffer,
  blankIndices: number[]
): Promise<Uint8Array> {
  const pdfDoc = await PDFDocument.load(arrayBuffer)
  const pageCount = pdfDoc.getPageCount()

  // Sort indices in descending order to avoid shifting problems
  const sorted = [...blankIndices].sort((a, b) => b - a)

  for (const index of sorted) {
    if (index >= 0 && index < pageCount) {
      pdfDoc.removePage(index)
    }
  }

  return await pdfDoc.save()
}
Enter fullscreen mode Exit fullscreen mode

Key detail: sorting in descending order. If you remove page 5 first, page 6 becomes page 5. By removing from highest index to lowest, positions stay stable.

Visual feedback

Users need to see which pages will be deleted before committing:

async function generateThumbnails(file: File, count: number): Promise<string[]> {
  const arrayBuffer = await file.arrayBuffer()
  const pdf = await pdfjs.getDocument({ data: arrayBuffer }).promise
  const thumbnails: string[] = []

  for (let i = 1; i <= Math.min(pdf.numPages, count); i++) {
    const page = await pdf.getPage(i)
    const viewport = page.getViewport({ scale: 0.3 }) // smaller thumbnail

    const canvas = document.createElement('canvas')
    canvas.width = viewport.width
    canvas.height = viewport.height
    const ctx = canvas.getContext('2d')!

    await page.render({ canvasContext: ctx, viewport }).promise
    thumbnails.push(canvas.toDataURL('image/jpeg', 0.7))
  }

  return thumbnails
}
Enter fullscreen mode Exit fullscreen mode

Each thumbnail gets a badge: "blank" (red), "likely blank" (yellow), or "has content" (green). Users can override by clicking any page.

Performance considerations

For large PDFs (100+ pages), pixel-by-pixel analysis gets slow. Two optimizations:

  1. Downscale before analyzing. Don't use full-resolution pages. Scale down to 200px width. Blank detection doesn't need megapixel precision.
  2. Process pages in parallel using Web Workers. Each worker handles a chunk of pages.
// Downscale example
const SCALE = 0.15 // 15% of original resolution
const viewport = page.getViewport({ scale: SCALE })
Enter fullscreen mode Exit fullscreen mode

At 15% scale, a 3000px-wide page becomes 450px. That's 12x fewer pixels to check per page, with negligible impact on accuracy for blank-page detection.

UX tips from a live tool

At en.sotool.top/remove-blank-pages, we learned:

  1. Show confidence scores, not binary decisions. "This page is 92% likely blank" is more honest than "blank" or "not blank."
  2. Let users override. Auto-detection isn't perfect. A single click to keep a flagged page builds trust.
  3. Warn about edge cases. Pages with very light text, faint watermarks, or custom backgrounds may be misclassified.
  4. Handle multi-size PDFs separately. Don't apply a global threshold if some pages are letter-sized and others are A4.

Going further

Blank page removal is straightforward in theory but tricky in practice because "blank" is subjective. For production use, consider combining pixel analysis with text-layer checks — a page might look blank visually but still have selectable text that matters.

Want to see the full source? github.com/sunshey/pdf-tool.


If you need desktop-grade PDF editing — batch blank page removal, OCR, or advanced export formats — check out Wondershare PDFelement.


Top comments (0)