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

使用Next.js与react-signature-canvas绘制签名时出现大量断点问题

问题

项目基于 React 18.2.0 + Next.js 13.0.3 + TypeScript 4.7.4,使用 react-signature-canvas@1.0.6 实现签名功能,本地运行正常,但部署到预生产/生产环境后,绘制签名时线条出现大量断点。尝试过更换兼容React17的组件版本、添加节流机制,问题仍未解决。

相关代码如下:

逻辑代码

const sigCanvas = useRef<SignatureCanvas>(null);

const [signature, setSignature] = useState<string>('');
let lastInvokeTime = 0;
const throttleInterval = 16;

const handleSignature = () => {
  const currentTime = Date.now();
  if (currentTime - lastInvokeTime > throttleInterval) {
    if (sigCanvas.current) {
      const canvas = sigCanvas.current.getTrimmedCanvas();
      const signature = canvas.toDataURL();
      setSignature(signature);
    }
    lastInvokeTime = currentTime;
  }
};

const [imageURL, setImageURL] = useState('');

const create = () => {
  if (sigCanvas.current) {
    const URL = sigCanvas.current.getTrimmedCanvas().toDataURL('image/png');
    setImageURL(URL);
    setOpenModal(false);
  }
};

组件渲染代码

<SignatureCanvas
  penColor="black"
  onBegin={handleSignature}
  minWidth={1}
  maxWidth={3}
  canvasProps={{
    className:
      'mt-10 w-full h-[200px] border-2 border-solid border-border_grey rounded-[10px] ',
  }}
  ref={sigCanvas}
/>

解决方案

一、修复现有组件的断点问题

  1. 修正Canvas尺寸匹配问题
    生产环境中常因CSS拉伸导致Canvas实际像素与显示尺寸不匹配,引发绘制坐标错位。解决方式:
  • 手动设置Canvas的width和height属性,与容器实际像素尺寸对齐:
    <SignatureCanvas
      penColor="black"
      onBegin={handleSignature}
      minWidth={1}
      maxWidth={3}
      canvasProps={{
        className: 'mt-10 w-full h-[200px] border-2 border-solid border-border_grey rounded-[10px]',
        width: 800, // 替换为容器实际像素宽度,可结合useWindowSize动态计算
        height: 200
      }}
      ref={sigCanvas}
    />
    
    或在组件挂载时动态适配:
    useEffect(() => {
      if (sigCanvas.current) {
        const canvas = sigCanvas.current.getCanvas();
        const container = canvas.parentElement;
        if (container) {
          const { width, height } = container.getBoundingClientRect();
          canvas.width = width;
          canvas.height = height;
        }
      }
    }, []);
    
  1. 调整事件监听时机
    当前onBegin仅在绘制开始时触发一次,无法覆盖整个绘制过程。建议改用onEnd事件(仅在绘制结束时更新签名),或移除实时更新逻辑,仅在需要获取签名时调用getTrimmedCanvas:
<SignatureCanvas
  penColor="black"
  onEnd={handleSignature} // 替换onBegin为onEnd
  minWidth={1}
  maxWidth={3}
  canvasProps={{
    className: 'mt-10 w-full h-[200px] border-2 border-solid border-border_grey rounded-[10px]',
  }}
  ref={sigCanvas}
/>
  1. 调整Next.js生产环境配置
    Next.js 13的生产优化可能干扰Canvas事件触发,尝试修改next.config.js:
module.exports = {
  reactStrictMode: false, // 关闭严格模式,避免组件重复渲染引发事件异常
}

二、手动实现签名功能(不依赖第三方组件)

如果上述方案无效,可基于原生Canvas API实现,可控性更强:

import { useRef, useEffect, useState } from 'react';

const SignaturePad = () => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const [isDrawing, setIsDrawing] = useState(false);
  const [imageURL, setImageURL] = useState('');
  let ctx: CanvasRenderingContext2D | null = null;

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    ctx = canvas.getContext('2d');
    if (!ctx) return;

    // 适配容器尺寸
    const container = canvas.parentElement;
    if (container) {
      const { width, height } = container.getBoundingClientRect();
      canvas.width = width;
      canvas.height = height;
    }

    // 设置画笔样式
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.strokeStyle = 'black';
    ctx.lineWidth = 2;
  }, []);

  const startDrawing = (e: React.MouseEvent | React.TouchEvent) => {
    setIsDrawing(true);
    if (!ctx) return;
    const pos = getEventPosition(e);
    ctx.beginPath();
    ctx.moveTo(pos.x, pos.y);
  };

  const draw = (e: React.MouseEvent | React.TouchEvent) => {
    if (!isDrawing || !ctx) return;
    const pos = getEventPosition(e);
    ctx.lineTo(pos.x, pos.y);
    ctx.stroke();
  };

  const stopDrawing = () => {
    setIsDrawing(false);
    ctx?.closePath();
  };

  const getEventPosition = (e: React.MouseEvent | React.TouchEvent) => {
    const canvas = canvasRef.current;
    if (!canvas) return { x: 0, y: 0 };
    const rect = canvas.getBoundingClientRect();
    let x, y;
    if ('touches' in e) {
      x = e.touches[0].clientX - rect.left;
      y = e.touches[0].clientY - rect.top;
    } else {
      x = e.clientX - rect.left;
      y = e.clientY - rect.top;
    }
    // 修正Canvas缩放比例
    const scaleX = canvas.width / rect.width;
    const scaleY = canvas.height / rect.height;
    return { x: x * scaleX, y: y * scaleY };
  };

  const createSignature = () => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const url = canvas.toDataURL('image/png');
    setImageURL(url);
    // 可添加关闭模态框等逻辑
  };

  return (
    <div className="mt-10 w-full">
      <canvas
        ref={canvasRef}
        className="w-full h-[200px] border-2 border-solid border-border_grey rounded-[10px]"
        onMouseDown={startDrawing}
        onMouseMove={draw}
        onMouseUp={stopDrawing}
        onMouseLeave={stopDrawing}
        onTouchStart={startDrawing}
        onTouchMove={draw}
        onTouchEnd={stopDrawing}
      />
      <button onClick={createSignature} className="mt-4">保存签名</button>
    </div>
  );
};

export default SignaturePad;

该实现直接监听Canvas的鼠标/触摸事件,适配了触摸场景和Canvas缩放问题,生产环境下稳定性更高。


内容的提问来源于stack exchange,提问作者Alejandro Fortes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 17:15:33