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

Canvas缩放致绘图模糊求助:签名板场景下的解决方案

问题解决:Canvas签名缩放后模糊的修复

问题根源

当前实现存在两个核心问题导致缩放后模糊:

  1. captureSignature中错误缩放绘图上下文,既干扰后续签名绘制,也没有真正生成高分辨率签名图像。
  2. 缩小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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 04:54:53