Converting PDF pages to images requires careful rendering to balance quality, memory usage, and speed.
Here's how to build a browser-based PDF to image converter with Vue 3.
The challenge: Quality vs performance
PDF to image conversion involves:
- Parsing PDF structure
- Rendering each page to a canvas
- Extracting image data at the desired DPI
- Managing memory for large documents
The stack
- Vue 3 with Composition API
- pdf.js for PDF parsing and rendering
- Canvas API for image extraction
- JSZip for ZIP bundle downloads
- Vite for bundling
The core implementation
<script setup lang="ts">
import { ref } from 'vue'
import * as pdfjsLib from 'pdfjs-dist'
import JSZip from 'jszip'
pdfjsLib.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjsLib.version}/pdf.worker.min.js`
const file = ref<File | null>(null)
const pages = ref<any[]>([])
const converting = ref(false)
const progress = ref(0)
const dpi = ref(200)
const format = ref<'png' | 'jpeg'>('png')
async function convertPdfToImages() {
if (!file.value) return
converting.value = true
progress.value = 0
const arrayBuffer = await file.value.arrayBuffer()
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise
pages.value = []
const scale = dpi.value / 72
const total = pdf.numPages
for (let i = 1; i <= total; i++) {
const page = await pdf.getPage(i)
const viewport = page.getViewport({ scale })
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 mimeType = format.value === 'png' ? 'image/png' : 'image/jpeg'
const dataUrl = canvas.toDataURL(mimeType, 0.92)
pages.value.push({ index: i, dataUrl, width: viewport.width, height: viewport.height })
progress.value = Math.round((i / total) * 100)
}
converting.value = false
}
async function downloadZip() {
const zip = new JSZip()
pages.value.forEach((page, i) => {
const ext = format.value === 'png' ? 'png' : 'jpg'
zip.file(`page-${String(i + 1).padStart(3, '0')}.${ext}`,
atob(page.dataUrl.split(',')[1]), { base64: true })
})
const blob = await zip.generateAsync({ type: 'blob' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'pdf-pages.zip'
a.click()
}
</script>
Key implementation details
1. DPI calculation
PDF.js uses a default scale of 1.0 (72 DPI). To get higher resolution:
const scale = dpi / 72
const viewport = page.getViewport({ scale })
2. Canvas rendering
Render each page sequentially to avoid memory overload:
await page.render({ canvasContext: ctx, viewport }).promise
3. Memory management
Process pages one at a time, not in parallel:
for (let i = 1; i <= total; i++) {
// render one page
// then move to next
}
4. ZIP bundling
Use JSZip to bundle all pages into a single download:
const zip = new JSZip()
pages.forEach((page) => {
zip.file(`page-${index}.${ext}`, base64Data)
})
Limitations
Large files
PDFs with 100+ high-resolution pages may cause memory issues.
Solution: Add a progress bar and process in batches.
Complex PDFs
Some PDFs with custom fonts or encrypted content may not render correctly.
Solution: Show a warning and suggest using desktop software.
Mobile performance
Mobile browsers have less memory and may struggle with large PDFs.
Solution: Limit max DPI or page count on mobile devices.
Summary
Building a browser-based PDF to image converter involves:
- Using pdf.js for rendering
- Calculating scale for desired DPI
- Processing pages sequentially
- Bundling results with JSZip
Try it at en.sotool.top/pdf-to-image.