Canvas缩放致绘图模糊求助:签名板场景下的解决方案
问题解决:Canvas签名缩放后模糊的修复
问题根源
当前实现存在两个核心问题导致缩放后模糊:
captureSignature中错误缩放绘图上下文,既干扰后续签名绘制,也没有真正生成高分辨率签名图像。- 缩小Canvas后直接按签名图像原尺寸绘制,Canvas会自动对图像进行像素插值缩放,引发模糊。
快速修复方案(适配现有代码)
修改签名的保存与重绘逻辑,确保保存原始尺寸的签名数据,重绘时根据Canvas当前尺寸等比例缩放绘制:
修改后的完整代码
var signatureCache = new Map(); // 用Map存储每个Canvas的原始签名数据(尺寸+图像) // 保存当前Canvas的签名原始数据 function captureSignature(canvas) { // 直接保存当前Canvas的尺寸和原始图像数据,不修改上下文缩放 signatureCache.set(canvas, { originalWidth: canvas.width, originalHeight: canvas.height, dataUrl: canvas.toDataURL('image/png', 1.0) }); } // 根据当前Canvas尺寸等比例重绘签名 function redrawSignature(canvas) { const cache = signatureCache.get(canvas); if (!cache) return; const context = canvas.getContext('2d'); const image = new Image(); image.onload = function() { context.clearRect(0, 0, canvas.width, canvas.height); // 计算等比例缩放尺寸,保持签名宽高比 const scaleX = canvas.width / cache.originalWidth; const scaleY = canvas.height / cache.originalHeight; const scale = Math.min(scaleX, scaleY); const drawWidth = cache.originalWidth * scale; const drawHeight = cache.originalHeight * scale; // 居中绘制(可根据需求调整) const offsetX = (canvas.width - drawWidth) / 2; const offsetY = (canvas.height - drawHeight) / 2; context.drawImage(image, offsetX, offsetY, drawWidth, drawHeight); }; image.src = cache.dataUrl; } // 切换Canvas尺寸逻辑不变,仅替换capture和redraw函数 function toggleCanvasSize(canvas, toggleButton) { captureSignature(canvas); if (!canvas.classList.contains('fullscreen')) { const originalWidth = canvas.width; const originalHeight = canvas.height; const aspectRatio = originalWidth / originalHeight; const newWidth = window.innerWidth - 10; const newHeight = newWidth / aspectRatio; canvas.width = newWidth; canvas.height = newHeight; canvas.style.marginRight = "-375px"; canvas.classList.add('fullscreen'); toggleButton.textContent = 'הקטן'; toggleButton.classList.remove('uk-button-success'); toggleButton.classList.add('uk-button-danger'); } else { canvas.width = 302; canvas.height = 122; canvas.style.marginRight = ''; canvas.style.marginLeft = ''; canvas.style.display = ''; canvas.classList.remove('fullscreen'); toggleButton.textContent = 'הגדל'; toggleButton.classList.remove('uk-button-danger'); toggleButton.classList.add('uk-button-success'); } redrawSignature(canvas); } // 以下函数保持不变 function getOrderBodyCanvas() { const canvas = document.querySelector('canvas[name="orderBody"]'); const toggleButton = document.getElementById('toggleSizeOrderBody'); toggleCanvasSize(canvas, toggleButton) } function getInstallationPricingCanvas() { const canvas = document.querySelector('canvas[name="installationpricing"]'); const toggleButton = document.getElementById('toggleSizeInstallationPricing'); toggleCanvasSize(canvas, toggleButton) } function clearCanvas(canvas) { const context = canvas.getContext('2d'); context.clearRect(0, 0, canvas.width, canvas.height); signatureCache.delete(canvas); // 清除对应缓存 } document.getElementById('toggleSizeOrderBody').addEventListener('click', getOrderBodyCanvas); document.getElementById('toggleSizeInstallationPricing').addEventListener('click', getInstallationPricingCanvas); document.getElementById('clearCanvasOrderBody').addEventListener('click', function() { const canvas = document.querySelector('canvas[name="orderBody"]'); if (canvas) { clearCanvas(canvas); } }); document.getElementById('clearCanvasInstallationPricing').addEventListener('click', function() { const canvas = document.querySelector('canvas[name="installationpricing"]'); if (canvas) { clearCanvas(canvas); } });
更优方案(矢量路径保存)
若要彻底避免缩放模糊,建议保存签名的路径坐标数据而非图像,缩放时重新绘制路径即可:
核心实现示例
// 为每个Canvas维护路径数据 const signaturePaths = new Map(); // 初始化签名绘制事件 function initSignatureCanvas(canvas) { let isDrawing = false; const path = []; signaturePaths.set(canvas, path); canvas.addEventListener('mousedown', (e) => { isDrawing = true; const rect = canvas.getBoundingClientRect(); path.push({ type: 'start', x: e.clientX - rect.left, y: e.clientY - rect.top }); }); canvas.addEventListener('mousemove', (e) => { if (!isDrawing) return; const rect = canvas.getBoundingClientRect(); path.push({ type: 'line', x: e.clientX - rect.left, y: e.clientY - rect.top }); redrawPath(canvas); }); canvas.addEventListener('mouseup', () => { isDrawing = false; }); } // 根据路径重绘签名 function redrawPath(canvas) { const path = signaturePaths.get(canvas); if (!path || path.length === 0) return; const context = canvas.getContext('2d'); context.clearRect(0, 0, canvas.width, canvas.height); context.lineWidth = 2; context.lineCap = 'round'; context.strokeStyle = '#000'; context.beginPath(); path.forEach((point) => { if (point.type === 'start') { context.moveTo(point.x, point.y); } else { context.lineTo(point.x, point.y); } }); context.stroke(); } // 切换尺寸时直接调用redrawPath即可 function toggleCanvasSize(canvas, toggleButton) { if (!canvas.classList.contains('fullscreen')) { // 放大逻辑不变 } else { // 缩小逻辑不变 } redrawPath(canvas); }
关键说明
- 快速修复方案改动小,适配现有代码,通过等比例缩放绘制解决模糊问题。
- 矢量路径方案从根源消除缩放模糊,适合对签名清晰度要求高的场景,但需要修改签名绘制的核心逻辑。
内容的提问来源于stack exchange,提问作者Itay
相关产品推荐
相关产品推荐

