Y
Published on

How to Capture Signature in React applications

Authors
  • avatar
    Name
    Yinhuan Yuan
    Twitter

Introduction

When developing React applications with Material-UI (MUI), following best practices helps ensure your application is maintainable, performant, and aligned with industry standards. Below are some key best practices to follow when working with MUI in React:

I'll explain several approaches to capture signatures in web applications and provide an example implementation of one of them.

Here are the main approaches for signature capture:

  1. Canvas-based Solutions:
  • Pros: Native browser support, lightweight, customizable
  • Cons: Requires more custom code for features
  • Best for: Simple signature requirements
  1. Library-based Solutions:
  • SignaturePad.js: Lightweight, popular choice
  • Fabric.js: Full featured canvas library
  • react-signature-canvas: React-specific solution
  • Pros: Ready-made features, good documentation
  • Cons: Additional bundle size
  1. SVG-based Solutions:
  • Pros: Scalable, smaller file size
  • Cons: More complex to implement
  • Best for: High-quality signature requirements

Let me create a simple but functional signature pad implementation using Canvas:

import React, { useRef, useState, useEffect } from 'react'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'

const SignaturePad = () => {
  const canvasRef = useRef(null)
  const [isDrawing, setIsDrawing] = useState(false)
  const [context, setContext] = useState(null)

  useEffect(() => {
    const canvas = canvasRef.current
    const ctx = canvas.getContext('2d')
    ctx.strokeStyle = '#000000'
    ctx.lineWidth = 2
    ctx.lineCap = 'round'
    setContext(ctx)

    // Handle device pixel ratio for crisp lines
    const dpr = window.devicePixelRatio || 1
    const rect = canvas.getBoundingClientRect()
    canvas.width = rect.width * dpr
    canvas.height = rect.height * dpr
    ctx.scale(dpr, dpr)
    canvas.style.width = `${rect.width}px`
    canvas.style.height = `${rect.height}px`
  }, [])

  const startDrawing = (e) => {
    const rect = canvasRef.current.getBoundingClientRect()
    const x = e.clientX - rect.left
    const y = e.clientY - rect.top

    context.beginPath()
    context.moveTo(x, y)
    setIsDrawing(true)
  }

  const draw = (e) => {
    if (!isDrawing) return

    const rect = canvasRef.current.getBoundingClientRect()
    const x = e.clientX - rect.left
    const y = e.clientY - rect.top

    context.lineTo(x, y)
    context.stroke()
  }

  const stopDrawing = () => {
    setIsDrawing(false)
    context.closePath()
  }

  const clearSignature = () => {
    const canvas = canvasRef.current
    context.clearRect(0, 0, canvas.width, canvas.height)
  }

  const saveSignature = () => {
    const canvas = canvasRef.current
    const dataUrl = canvas.toDataURL('image/png')

    // Create a temporary link to download the signature
    const link = document.createElement('a')
    link.download = 'signature.png'
    link.href = dataUrl
    link.click()
  }

  return (
    <Card className="w-full max-w-2xl">
      <CardHeader>
        <CardTitle>Signature Pad</CardTitle>
      </CardHeader>
      <CardContent>
        <div className="rounded-lg border bg-white p-4">
          <canvas
            ref={canvasRef}
            className="h-48 w-full cursor-crosshair touch-none rounded border"
            onMouseDown={startDrawing}
            onMouseMove={draw}
            onMouseUp={stopDrawing}
            onMouseLeave={stopDrawing}
          />
        </div>
      </CardContent>
      <CardFooter className="flex justify-end space-x-2">
        <Button variant="outline" onClick={clearSignature}>
          Clear
        </Button>
        <Button onClick={saveSignature}>Save</Button>
      </CardFooter>
    </Card>
  )
}

export default SignaturePad

This implementation provides a basic but functional signature capture solution. Here are some additional solutions and enhancements you might consider:

  1. Touch-enabled Enhancement:
// Add these event listeners for touch support
onTouchStart={(e) => {
  e.preventDefault();
  const touch = e.touches[0];
  startDrawing(touch);
}}
onTouchMove={(e) => {
  e.preventDefault();
  const touch = e.touches[0];
  draw(touch);
}}
onTouchEnd={stopDrawing}
  1. Using SignaturePad.js library:
import SignaturePad from 'signature_pad'
// Initialize with
const signaturePad = new SignaturePad(canvas, {
  minWidth: 0.5,
  maxWidth: 2.5,
  penColor: 'black',
})
  1. Advanced Features You Might Want to Add:
  • Undo/Redo functionality
  • Different pen colors and sizes
  • Signature validation (minimum length/points)
  • Base64 encoding for server submission
  • Pressure sensitivity support
  • Background lines or grid
  • Mobile-specific optimizations
  1. Server Integration Example:
const submitSignature = async () => {
  const dataUrl = canvasRef.current.toDataURL()
  try {
    const response = await fetch('/api/signatures', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ signature: dataUrl }),
    })
    // Handle response
  } catch (error) {
    // Handle error
  }
}
  1. For Production Considerations:
  • Add error boundaries
  • Implement loading states
  • Add proper validation
  • Include accessibility features
  • Add proper touch event handling
  • Implement proper error messages
  • Add signature verification
  • Implement proper data storage

Would you like me to modify the implementation to include any of these additional features or focus on a specific aspect of signature capture?