You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于PDF-LIB开发类似iLovePDF的Laravel签名PDF Web应用?

实现Laravel网站的手写签名PDF编辑器方案

一、前端核心模块(签名交互+PDF预览)

前端需完成PDF渲染、签名绘制、签名拖拽定位三个核心功能,以下是具体实现:

1. PDF预览渲染

使用pdfjs-dist(Mozilla官方PDF渲染库)实现前端PDF预览:

  • 安装依赖:
npm install pdfjs-dist
  • 初始化PDF加载逻辑:
import * as pdfjsLib from 'pdfjs-dist/webpack';
import 'pdfjs-dist/webpack/build/pdf.worker.entry';

// 全局存储PDF实例和视口信息,用于后续坐标转换
let currentPDF = null;
let currentViewport = null;

async function loadPDF(pdfUrl) {
  const pdf = await pdfjsLib.getDocument(pdfUrl).promise;
  const page = await pdf.getPage(1);
  currentViewport = page.getViewport({ scale: 1 });
  
  const canvas = document.getElementById('pdf-preview');
  const ctx = canvas.getContext('2d');
  canvas.height = currentViewport.height;
  canvas.width = currentViewport.width;
  
  await page.render({ canvasContext: ctx, viewport: currentViewport }).promise;
  currentPDF = pdf;
}

2. 手写签名画布

用轻量库signature_pad实现签名绘制,比原生Canvas更省心:

  • 安装依赖:
npm install signature_pad
  • 初始化签名画布:
import SignaturePad from 'signature_pad';

const sigCanvas = document.getElementById('signature-canvas');
const sigPad = new SignaturePad(sigCanvas, {
  backgroundColor: 'rgba(255,255,255,0)',
  penColor: '#000000',
  minWidth: 1,
  maxWidth: 2
});

// 绑定清空签名按钮
document.getElementById('clear-sig').addEventListener('click', () => sigPad.clear());

3. 签名拖拽定位

将签名转为图片,添加到PDF预览区域并支持拖拽调整位置:

function addSignatureToPreview() {
  if (sigPad.isEmpty()) return;
  
  const sigImg = document.createElement('img');
  sigImg.src = sigPad.toDataURL('image/png');
  sigImg.style.cssText = `
    position: absolute;
    width: 160px;
    cursor: move;
    z-index: 10;
  `;
  
  // 原生拖拽逻辑
  let isDragging = false;
  let startX, startY;
  
  sigImg.addEventListener('mousedown', e => {
    isDragging = true;
    startX = e.clientX - sigImg.offsetLeft;
    startY = e.clientY - sigImg.offsetTop;
  });
  
  document.addEventListener('mousemove', e => {
    if (isDragging) {
      sigImg.style.left = `${e.clientX - startX}px`;
      sigImg.style.top = `${e.clientY - startY}px`;
    }
  });
  
  document.addEventListener('mouseup', () => isDragging = false);
  
  document.getElementById('pdf-container').appendChild(sigImg);
}

二、后端Laravel结合PDF-LIB完成签名嵌入

前端将签名数据、位置信息传给后端,用PDF-LIB(前端完成嵌入后传后端保存更高效)处理最终PDF:

1. 前端用PDF-LIB嵌入签名

import { PDFDocument } from 'pdf-lib';

async function generateSignedPDF(originalPdfUrl) {
  const sigElement = document.querySelector('#pdf-container img');
  if (!sigElement) return;
  
  // 转换网页坐标到PDF坐标系(PDF以左下角为原点)
  const scale = currentViewport.scale;
  const pdfPos = {
    x: parseFloat(sigElement.style.left) / scale,
    y: currentViewport.height - (parseFloat(sigElement.style.top) / scale) - (parseFloat(sigElement.style.height) / scale),
    width: parseFloat(sigElement.style.width) / scale,
    height: parseFloat(sigElement.style.height) / scale
  };
  
  // 加载原始PDF并嵌入签名
  const pdfBytes = await fetch(originalPdfUrl).then(res => res.arrayBuffer());
  const pdfDoc = await PDFDocument.load(pdfBytes);
  const sigImage = await pdfDoc.embedPng(sigPad.toDataURL('image/png'));
  
  const page = pdfDoc.getPage(0);
  page.drawImage(sigImage, pdfPos);
  
  // 生成Blob格式的签名PDF
  return new Blob([await pdfDoc.save()], { type: 'application/pdf' });
}

2. Laravel后端接收并保存PDF

  • 添加路由:
// routes/web.php
Route::post('/save-signed-pdf', [PdfController::class, 'store'])->name('pdf.signed.store');
  • 控制器逻辑:
// app/Http/Controllers/PdfController.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

public function store(Request $request)
{
    $request->validate([
        'signed_pdf' => 'required|file|mimes:pdf|max:10240'
    ]);
    
    $file = $request->file('signed_pdf');
    $fileName = 'signed_' . uniqid() . '.pdf';
    $path = $file->storeAs('public/signed_pdfs', $fileName);
    
    return response()->json([
        'success' => true,
        'download_url' => Storage::url($path)
    ]);
}

3. 前端上传签名PDF

async function saveSignedPDF() {
  const signedBlob = await generateSignedPDF('/storage/original.pdf');
  const formData = new FormData();
  formData.append('signed_pdf', signedBlob, 'signed_document.pdf');
  
  const response = await fetch('/save-signed-pdf', {
    method: 'POST',
    headers: {
      'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
    },
    body: formData
  });
  
  const result = await response.json();
  if (result.success) {
    // 提示用户并提供下载链接
    alert('签名PDF已保存');
    document.getElementById('download-link').href = result.download_url;
  }
}

三、功能优化建议

  • 多页PDF支持:扩展PDF预览逻辑,添加页码切换按钮,记录签名对应的页码
  • 签名样式自定义:添加颜色选择器、笔画粗细滑块,修改signature_pad配置
  • 响应式适配:根据屏幕尺寸调整PDF缩放比例,确保移动端正常交互
  • 撤销/重做:利用signature_pad的undo()和redo()方法实现签名回退功能

内容的提问来源于stack exchange,提问作者Muhammad Nafis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 22:27:44