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

如何在JavaScript中实现类似Adobe Illustrator的平滑画笔笔触?

实现平滑书法画笔笔触的优化方案

针对你当前画笔笔触存在锯齿、不够流畅的问题,可以通过以下几个关键优化来达到专业绘图应用的平滑效果:

核心优化点

  • 高DPI画布适配:匹配屏幕像素密度,避免因画布分辨率不足导致的锯齿
  • Catmull-Rom样条路径平滑:对鼠标轨迹点进行插值处理,生成连续流畅的曲线
  • 增强抗锯齿配置:开启高质量图像平滑,配合圆角线帽/线连接强化边缘柔和度
  • 轨迹点缓存机制:收集鼠标移动轨迹点后批量绘制,减少分段直线的生硬感

修改后的完整代码

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

let isDrawing = false;
let lastX = 0;
let lastY = 0;
let undoStack = [];
// 存储鼠标轨迹点(包含对应线宽)
let points = [];
// 适配屏幕像素密度
const dpr = window.devicePixelRatio || 1;

// 初始化画布尺寸与配置
function initCanvas() {
  const rect = canvas.getBoundingClientRect();
  canvas.width = rect.width * dpr;
  canvas.height = rect.height * dpr;
  canvas.style.width = `${rect.width}px`;
  canvas.style.height = `${rect.height}px`;
  ctx.scale(dpr, dpr);
  
  // 开启高质量抗锯齿
  ctx.imageSmoothingEnabled = true;
  ctx.imageSmoothingQuality = "high";
}

// 用二次贝塞尔曲线实现平滑路径(简化版Catmull-Rom样条)
function drawSmoothPath(points) {
  if (points.length < 3) return;
  
  ctx.beginPath();
  ctx.moveTo(points[0].x, points[0].y);
  
  // 遍历生成平滑曲线段
  for (let i = 1; i < points.length - 2; i++) {
    const p0 = points[i];
    const p1 = points[i + 1];
    const cpX = (p0.x + p1.x) / 2;
    const cpY = (p0.y + p1.y) / 2;
    ctx.quadraticCurveTo(p0.x, p0.y, cpX, cpY);
  }
  
  // 绘制最后一段曲线
  const lastPoint = points[points.length - 1];
  const secondLast = points[points.length - 2];
  ctx.quadraticCurveTo(secondLast.x, secondLast.y, lastPoint.x, lastPoint.y);
  
  ctx.stroke();
}

function startDrawing(e) {
  isDrawing = true;
  [lastX, lastY] = [e.offsetX, e.offsetY];
  // 初始化轨迹点数组
  points = [{x: lastX, y: lastY, width: 11}];
}

function draw(e) {
  if (!isDrawing) return;
  
  const currentX = e.offsetX;
  const currentY = e.offsetY;
  const dist = Math.sqrt((lastX - currentX) ** 2 + (lastY - currentY) ** 2);
  const maxLineWidth = 11;
  const minLineWidth = 4;
  const maxDistance = 75;
  let lineWidth = maxLineWidth - (dist / maxDistance) * (maxLineWidth - minLineWidth);
  lineWidth = Math.max(Math.min(lineWidth, maxLineWidth), minLineWidth);
  
  // 记录当前轨迹点及对应线宽
  points.push({x: currentX, y: currentY, width: lineWidth});
  
  // 清空画布并恢复上一次绘制内容
  ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
  if (undoStack.length > 0) {
    ctx.putImageData(undoStack[undoStack.length - 1], 0, 0);
  }
  
  // 设置画笔样式
  ctx.lineCap = "round";
  ctx.lineJoin = "round";
  ctx.strokeStyle = "#000";
  ctx.lineWidth = lineWidth;
  
  // 绘制平滑路径
  drawSmoothPath(points);
  
  [lastX, lastY] = [currentX, currentY];
}

function stopDrawing() {
  isDrawing = false;
  // 保存当前画布状态到撤销栈
  undoStack.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
}

// 初始化画布并监听窗口尺寸变化
initCanvas();
window.addEventListener("resize", initCanvas);

canvas.addEventListener("mousedown", startDrawing);
canvas.addEventListener("mousemove", draw);
canvas.addEventListener("mouseup", stopDrawing);
canvas.addEventListener("mouseout", stopDrawing);

关键改动说明

  1. 高DPI适配:通过devicePixelRatio缩放画布实际像素,解决Retina屏幕下绘制模糊、锯齿严重的问题。
  2. 轨迹点缓存:不再每次鼠标移动都绘制小段直线,而是收集所有轨迹点后批量生成平滑曲线,避免分段生硬感。
  3. 平滑路径生成:用二次贝塞尔曲线对轨迹点插值,让笔触过渡自然流畅,替代原有的直线段拼接。
  4. 抗锯齿增强:开启high级别的图像平滑,配合圆角线帽和线连接,进一步弱化边缘锯齿。
  5. 窗口适配:监听窗口resize事件,自动调整画布尺寸,保证不同屏幕下的绘制效果一致。

当前笔触效果:
Jagged Strokes

优化后,笔触会变得流畅平滑,边缘锯齿基本消失,同时保留书法笔的粗细随速度变化的特性。

内容的提问来源于stack exchange,提问作者Quốc Cường

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 10:12:06