Signing PDFs in the browser involves capturing user input and embedding it into the PDF document. Here's how to build a browser-based PDF signing tool with Vue 3 and pdf-lib.
The challenge: Signature capture and placement
PDF signing requires:
- Capturing handwritten signatures via canvas
- Converting signatures to images
- Positioning signatures on specific pages
- Preserving signature appearance and quality
The stack
- Vue 3 with Composition API
- pdf-lib for PDF manipulation
- Canvas API for signature drawing
- Vite for bundling
The core implementation
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { PDFDocument } from 'pdf-lib'
const file = ref<File | null>(null)
const signatureImage = ref<string | null>(null)
const isDrawing = ref(false)
const signing = ref(false)
const result = ref<Uint8Array | null>(null)
const canvasRef = ref<HTMLCanvasElement | null>(null)
let ctx: CanvasRenderingContext2D | null = null
function startDrawing(e: MouseEvent | TouchEvent) {
isDrawing.value = true
draw(e)
}
function stopDrawing() {
isDrawing.value = false
}
function draw(e: MouseEvent | TouchEvent) {
if (!isDrawing.value || !ctx) return
const canvas = canvasRef.value!
const rect = canvas.getBoundingClientRect()
let x, y
if (e instanceof MouseEvent) {
x = e.clientX - rect.left
y = e.clientY - rect.top
} else {
x = e.touches[0].clientX - rect.left
y = e.touches[0].clientY - rect.top
}
ctx.lineTo(x, y)
ctx.stroke()
}
function clearCanvas() {
if (ctx && canvasRef.value) {
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
}
}
async function signPdf() {
if (!file.value || !signatureImage.value) return
signing.value = true
// Convert canvas to image data URL
const canvas = canvasRef.value!
signatureImage.value = canvas.toDataURL('image/png')
const arrayBuffer = await file.value.arrayBuffer()
const pdf = await PDFDocument.load(arrayBuffer)
const pages = pdf.getPages()
const page = pages[0]
const signatureBytes = await fetch(signatureImage.value)
.then(res => res.arrayBuffer())
const pngImage = await pdf.embedPng(signatureBytes)
page.drawImage(pngImage, {
x: 50,
y: 100,
width: 200,
height: 60,
})
result.value = await pdf.save()
signing.value = false
}
</script>
Key implementation details
1. Canvas signature capture
Use Canvas API to capture handwritten signatures:
const canvas = document.createElement('canvas')
canvas.width = 400
canvas.height = 150
const ctx = canvas.getContext('2d')!
ctx.strokeStyle = '#000'
ctx.lineWidth = 2
ctx.lineCap = 'round'
2. Touch and mouse events
Support both mouse and touch input:
canvas.addEventListener('mousedown', startDrawing)
canvas.addEventListener('mousemove', draw)
canvas.addEventListener('mouseup', stopDrawing)
canvas.addEventListener('mouseout', stopDrawing)
// Touch events
canvas.addEventListener('touchstart', startDrawing)
canvas.addEventListener('touchmove', draw)
canvas.addEventListener('touchend', stopDrawing)
3. Signature to image conversion
Convert canvas drawing to PNG for embedding:
const signatureDataUrl = canvas.toDataURL('image/png')
const response = await fetch(signatureDataUrl)
const arrayBuffer = await response.arrayBuffer()
const pngImage = await pdf.embedPng(arrayBuffer)
4. Positioning the signature
Place signature at specific coordinates:
page.drawImage(pngImage, {
x: 50, // Left margin
y: 100, // Bottom position
width: 200,
height: 60,
})
Limitations
No real-time preview
Can't show signature placement before applying.
Solution: Use a separate preview canvas.
Limited signature styles
Basic drawing only; no fonts or styles.
Solution: Support image upload for more options.
Mobile compatibility
Touch events need careful handling.
Solution: Test on multiple devices.
Summary
Building a browser-based PDF signing tool involves:
- Creating a canvas for signature drawing
- Capturing user input (mouse/touch)
- Converting signature to image
- Embedding in PDF at specified position
Try it at en.sotool.top/sign-pdf.