Canvas电子签名功能在Android设备上无法正常运行的技术问询
问题分析与解决方案
核心问题原因
- 页面滚动干扰:Android设备触摸Canvas时会触发页面滚动,导致
clientX/clientY坐标随页面滚动偏移,签名轨迹错位。 - Canvas坐标映射错误:Canvas的元素属性尺寸(
width/height)与CSS显示尺寸不一致时,直接用显示坐标计算画布位置会导致轨迹偏移。 - 未处理
pointercancel事件:Android部分场景下(如系统触发滚动)会触发pointercancel,此时未终止签名状态,导致异常绘制。
具体修复步骤
1. 阻止页面滚动干扰
修改pointerdown事件监听的passive属性为false,并在handlePointerDown中阻止默认行为,避免触摸Canvas时页面滚动:
const handlePointerDown = (event) => { event.preventDefault(); // 阻止页面滚动等默认行为 writingMode = true; ctx.beginPath(); const [positionX, positionY] = getCursorPosition(event); ctx.moveTo(positionX, positionY); } // 修改事件监听的第三个参数 canvas.addEventListener('pointerdown', handlePointerDown, { passive: false });
2. 修正Canvas坐标映射
更新getCursorPosition函数,将触摸的显示坐标转换为Canvas画布的实际坐标,适配CSS缩放后的尺寸:
const getCursorPosition = (event) => { const rect = event.target.getBoundingClientRect(); // 计算显示尺寸与画布实际尺寸的比例 const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; // 转换为画布实际坐标 const positionX = (event.clientX - rect.x) * scaleX; const positionY = (event.clientY - rect.y) * scaleY; return [positionX, positionY]; }
3. 处理pointercancel事件
添加pointercancel事件监听,在触摸被系统中断时终止签名状态:
const handlePointerCancel = () => { writingMode = false; } canvas.addEventListener('pointercancel', handlePointerCancel, { passive: true });
4. 确保Canvas样式适配
在样式中添加Canvas的自适应规则,避免拉伸变形:
.signature-pad { border: 1px solid #ccc; max-width: 100%; height: auto; touch-action: none; /* 直接禁用触摸相关的默认行为,增强兼容性 */ }
完整修复后的代码
<!DOCTYPE html> <html lang="it"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Singature Pad</title> <link rel="stylesheet" href="{{ url_for('static', filename='app.css') }}"> <style> .signature-pad { border: 1px solid #ccc; max-width: 100%; height: auto; touch-action: none; } </style> </head> <body> <form class="signature-pad-form" method="POST"> <canvas height="250" width="500" class="signature-pad"></canvas> <button class="clear-button">Cancella</button> <button class="submit-button" type="submit">Salva</button> </form> <script> const canvas = document.querySelector('canvas'); const form = document.querySelector('.signature-pad-form'); const clearButton = document.querySelector('.clear-button'); const ctx = canvas.getContext('2d'); ctx.fillStyle = 'white'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.strokeStyle = 'black'; ctx.lineWidth = 3; ctx.lineJoin = ctx.lineCap = 'round'; let writingMode = false; const handlePointerDown = (event) => { event.preventDefault(); writingMode = true; ctx.beginPath(); const [positionX, positionY] = getCursorPosition(event); ctx.moveTo(positionX, positionY); } const handlePointerUp = () => { writingMode = false; } const handlePointerCancel = () => { writingMode = false; } const handlePointerMove = (event) => { if (!writingMode) return; const [positionX, positionY] = getCursorPosition(event); ctx.lineTo(positionX, positionY); ctx.stroke(); } const getCursorPosition = (event) => { const rect = event.target.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; const positionX = (event.clientX - rect.x) * scaleX; const positionY = (event.clientY - rect.y) * scaleY; return [positionX, positionY]; } form.addEventListener('submit', (event) => { event.preventDefault(); const imageURL = canvas.toDataURL('image/jpeg'); const imageBlob = dataURItoBlob(imageURL); const formData = new FormData(); formData.append('firma', imageBlob); fetch('/firma', { method: 'POST', body: formData }) .then(response => { if (response.ok) { clearPad(); form.reset(); window.location.href = "{{ url_for('succ') }}" } else { console.error('Errore durante il salvataggio della firma.'); } }) .catch(error => { console.error('Si è verificato un errore:', error); }); }); const clearPad = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'white'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.strokeStyle = 'black'; } clearButton.addEventListener('click', (event) => { event.preventDefault(); clearPad(); }); canvas.addEventListener('pointerdown', handlePointerDown, { passive: false }); canvas.addEventListener('pointerup', handlePointerUp, { passive: true }); canvas.addEventListener('pointercancel', handlePointerCancel, { passive: true }); canvas.addEventListener('pointermove', handlePointerMove, { passive: true }); function dataURItoBlob(dataURI) { const byteString = atob(dataURI.split(',')[1]); const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; const ab = new ArrayBuffer(byteString.length); const ia = new Uint8Array(ab); for (let i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } return new Blob([ab], { type: mimeString }); } </script> </body> </html>
内容的提问来源于stack exchange,提问作者Lendy345
相关产品推荐
相关产品推荐

