广告位 · 页头(后台-模块管理-新建模块后替换此区域)

如何使用jQuery实现Canvas涂鸦板并导出为PNG图片?

在网页中实现一个简单的涂鸦板,核心是让画布接收鼠标或触摸轨迹,并把这些轨迹实时渲染到Canvas上。使用jQuery可以快速完成事件监听和DOM操作,而导出为PNG图片则依赖Canvas自带的toDataURL方法。整个过程不需要插件,也不需要服务端参与,适合在线签名、图片标注、白板工具等轻量场景。

如何使用jQuery实现Canvas涂鸦板并导出为PNG图片?

一、页面结构与基础配置

涂鸦板的HTML结构不复杂,一个<canvas>元素加上几个控制按钮即可。比较容易被忽略的是<canvas>的width和height属性与CSS宽度高度之间的关系。前者决定画布的实际像素坐标,后者只影响页面显示尺寸。如果两者不一致,绘图坐标与鼠标位置之间就会出现偏差。

先给canvas设置固定绘图区域,设置为800×500像素,同时在CSS里设置边框和白色背景。白色背景可以让导出的PNG图片不透明,并且在视觉上更接近纸张。页面引入jQuery后,jQuery对象通过$('#doodle')[0]转换为原生DOM对象,这样才能调用getContext方法。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>jQuery Canvas 涂鸦板</title>
  <style>
    #doodle {
      border: 1px solid #ccc;
      cursor: crosshair;
      background: #fff;
    }
    .toolbar {
      margin-bottom: 10px;
    }
  </style>
</head>
<body>
  <div class="toolbar">
    <button id="clearBtn">清空</button>
    <button id="exportBtn">导出PNG</button>
    <span id="status"></span>
  </div>
  <canvas id="doodle" width="800" height="500"></canvas>
  <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
  <script>
    // 绘图逻辑将在下一个小节实现
  </script>
</body>
</html>

这里把导出和清空按钮放在画布上方,状态区域用于提示导出结果。后面所有事件都基于这个结构来扩展。

二、用jQuery绑定鼠标与触摸绘图事件

绘图的核心是三个鼠标事件mousedown、mousemove和mouseup。在mousedown阶段记录起始点并开启绘制标志,在mousemove阶段不断连接新的坐标点并调用stroke绘制,在mouseup阶段结束当前路径。移动端还需要处理touchstart、touchmove和touchend。

坐标换算要格外注意。鼠标事件中的clientX和clientY是相对浏览器视口的位置,而Canvas绘图使用自己的像素坐标。通过getBoundingClientRect获取canvas在页面中的位置,再用client坐标减去左上角偏移,最后乘以实际像素与显示尺寸的比例,就能得到准确的绘制坐标。这样做可以同时兼容CSS缩放和移动端页面缩放。

$(function () {
  var canvas = $('#doodle')[0];
  var ctx = canvas.getContext('2d');
  var drawing = false;
  var lastX = 0;
  var lastY = 0;
  ctx.lineWidth = 3;
  ctx.lineCap = 'round';
  ctx.lineJoin = 'round';
  ctx.strokeStyle = '#333';
  function getPos(e) {
    var rect = canvas.getBoundingClientRect();
    var clientX, clientY;
    if (e.originalEvent.touches) {
      var touch = e.originalEvent.touches[0];
      clientX = touch.clientX;
      clientY = touch.clientY;
    } else {
      clientX = e.clientX;
      clientY = e.clientY;
    }
    return {
      x: (clientX - rect.left) * (canvas.width / rect.width),
      y: (clientY - rect.top) * (canvas.height / rect.height)
    };
  }
  $('#doodle').on('mousedown touchstart', function (e) {
    e.preventDefault();
    drawing = true;
    var pos = getPos(e);
    lastX = pos.x;
    lastY = pos.y;
    ctx.beginPath();
    ctx.moveTo(lastX, lastY);
  });
  $(document).on('mousemove touchmove', function (e) {
    if (!drawing) return;
    e.preventDefault();
    var pos = getPos(e);
    ctx.lineTo(pos.x, pos.y);
    ctx.stroke();
    lastX = pos.x;
    lastY = pos.y;
  });
  $(document).on('mouseup touchend', function () {
    drawing = false;
    ctx.closePath();
  });
});

这里将mousemove和touchmove绑定在document上而不是canvas上,是为了避免鼠标快速移动时移出画布导致线条中断。由于移动端触摸事件不会触发mousemove,所以需要同时绑定touch事件。调用preventDefault可以阻止触摸滚动页面。

三、导出为PNG图片

涂鸦完成后,导出PNG的核心方法只有一行:canvas.toDataURL('image/png')。它会返回一个base64格式的字符串,可以直接赋值给下载链接的href属性。为了让浏览器主动下载而不是跳转,需要创建一个不可见的下载链接,设置download属性,然后触发click事件。

$('#exportBtn').on('click', function () {
  var url = canvas.toDataURL('image/png');
  var link = document.createElement('a');
  link.href = url;
  link.download = 'doodle.png';
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
  $('#status').text('已导出PNG图片');
});

使用toDataURL导出的图片默认会保留Canvas的透明区域。如果画布没有先填充白色,PNG背景可能是透明的。部分图片查看器会用黑色或网格显示透明区域,所以通常需要在初始化时执行ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, canvas.width, canvas.height);,或者在导出前临时填充背景。

toDataURL会生成体积较大的base64数据,如果画布面积很大,建议改用toBlob方法。toBlob直接生成二进制Blob对象,再通过URL.createObjectURL生成临时下载地址,下载完成后调用URL.revokeObjectURL释放内存。两种方式都能得到PNG文件,toBlob在处理高清画布时更节约内存。

四、常见问题与优化建议

高分屏设备上直接使用CSS像素绘制会导致图形模糊。原因是Canvas内部像素与实际屏幕物理像素不匹配。解决思路是先读取window.devicePixelRatio,把canvas的width和height属性设置为显示尺寸乘以该倍率,再用CSS控制显示尺寸,最后在绘图上下文中调用ctx.scale(ratio, ratio)。这样线条会按照更高分辨率渲染,导出图片也会更清晰。

频繁触发mousemove时,每次移动都执行stroke可能会造成性能抖动,尤其在移动设备上。如果只是简单涂鸦,当前方案基本够用;如果需要加入更复杂的笔刷或大尺寸画布,可以将坐标点先存入数组,再通过requestAnimationFrame集中绘制。这样做可以避免同一次事件循环内重复渲染。

清空功能可以通过ctx.clearRect再重新填充背景实现。撤销功能则需要额外的数据模型,常见做法是保存每次绘制完成后的图片数据,回退时重新绘制上一步的图像。涂鸦板整体逻辑并不复杂,掌握Canvas路径绘制和事件坐标换算后,可以根据业务需求灵活扩展颜色选择、笔刷粗细、橡皮擦等功能。

广告位 · 文章底部(后台-模块管理替换此区域)