使用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} />
解决方案
一、修复现有组件的断点问题
- 修正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; } } }, []);
- 调整事件监听时机
当前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} />
- 调整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
相关产品推荐
相关产品推荐

