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

如何用SVG与JavaScript实现类似Illustrator的闭合区域识别功能

实现Kufi排版SVG网格的闭合区域检测与着色(类似Illustrator油漆桶工具)

针对你开发Kufi排版辅助网站时遇到的闭合区域检测与着色需求,以下是分步骤的解决方案:

步骤1:将基础SVG元素转换为路径(Path)

SVG的<circle>、<rect>等基础元素无法直接进行精确的相交计算,需先转换为<path>元素。可以通过手动生成路径数据实现转换:

// 转换圆形为路径
function circleToPath(circle) {
  const cx = circle.cx.baseVal.value;
  const cy = circle.cy.baseVal.value;
  const r = circle.r.baseVal.value;
  const d = `M ${cx - r},${cy} A ${r},${r} 0 1,1 ${cx + r},${cy} A ${r},${r} 0 1,1 ${cx - r},${cy} Z`;
  const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
  path.setAttribute("d", d);
  path.setAttribute("fill", "none");
  path.setAttribute("stroke", "black");
  path.setAttribute("stroke-width", circle.getAttribute("stroke-width"));
  circle.parentNode.replaceChild(path, circle);
  return path;
}

// 转换旋转矩形为路径
function rectToPath(rect) {
  const transform = rect.transform.baseVal.consolidate();
  const matrix = transform ? transform.matrix : null;
  const x = rect.x.baseVal.value;
  const y = rect.y.baseVal.value;
  const width = rect.width.baseVal.value;
  const height = rect.height.baseVal.value;

  // 生成矩形四个顶点并应用变换
  const points = [
    {x, y},
    {x: x + width, y},
    {x: x + width, y: y + height},
    {x, y: y + height}
  ];
  if (matrix) {
    points.forEach(p => {
      const newX = p.x * matrix.a + p.y * matrix.c + matrix.e;
      const newY = p.x * matrix.b + p.y * matrix.d + matrix.f;
      p.x = newX;
      p.y = newY;
    });
  }

  const d = `M ${points[0].x},${points[0].y} L ${points[1].x},${points[1].y} L ${points[2].x},${points[2].y} L ${points[3].x},${points[3].y} Z`;
  const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
  path.setAttribute("d", d);
  path.setAttribute("fill", "none");
  path.setAttribute("stroke", "black");
  path.setAttribute("stroke-width", rect.getAttribute("stroke-width"));
  rect.parentNode.replaceChild(path, rect);
  return path;
}

// 批量转换SVG中的图形
const svg = document.querySelector("svg");
svg.querySelectorAll("circle").forEach(circleToPath);
svg.querySelectorAll("rect").forEach(rectToPath);

步骤2:利用网格对称性直接生成闭合区域

你的Kufi网格具有明显的极坐标对称性(环形+旋转等分线),无需复杂的相交检测,可直接预计算所有扇形闭合区域:

const centerX = 500;
const centerY = 500;
// 从你的SVG中提取的环形半径列表
const radii = [20,30,50,60,80,90,110,120,140,150,170,180,200,210,230,240,260,270,290,300];
// 10等分的角度(0°、18°...162°)
const angles = Array.from({length:10}, (_,i) => i*18);

const regions = [];

// 生成每个环形扇形区域
for (let rIdx = 0; rIdx < radii.length -1; rIdx++) {
  const innerR = radii[rIdx];
  const outerR = radii[rIdx+1];
  
  for (let aIdx = 0; aIdx < angles.length; aIdx++) {
    const startAngle = angles[aIdx] * Math.PI / 180;
    const endAngle = angles[(aIdx+1)%angles.length] * Math.PI / 180;
    
    // 构建扇形路径数据
    const d = `
      M ${centerX + innerR * Math.cos(startAngle)}, ${centerY + innerR * Math.sin(startAngle)}
      A ${innerR},${innerR} 0 0,1 ${centerX + innerR * Math.cos(endAngle)}, ${centerY + innerR * Math.sin(endAngle)}
      L ${centerX + outerR * Math.cos(endAngle)}, ${centerY + outerR * Math.sin(endAngle)}
      A ${outerR},${outerR} 0 0,0 ${centerX + outerR * Math.cos(startAngle)}, ${centerY + outerR * Math.sin(startAngle)}
      Z
    `;
    
    // 创建区域路径元素(透明填充,用于检测)
    const regionPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
    regionPath.setAttribute("d", d);
    regionPath.setAttribute("fill", "transparent");
    regionPath.setAttribute("stroke", "none");
    regionPath.setAttribute("class", "kufi-region");
    svg.appendChild(regionPath);
    
    regions.push({
      path: new Path2D(d),
      element: regionPath
    });
  }
}

步骤3:实现鼠标悬停检测与着色功能

使用SVG的isPointInPath方法检测鼠标位置,实现实时高亮和点击着色:

// 鼠标悬停高亮
svg.addEventListener("mousemove", (e) => {
  const point = svg.createSVGPoint();
  point.x = e.clientX;
  point.y = e.clientY;
  const svgPoint = point.matrixTransform(svg.getScreenCTM().inverse());
  
  // 重置所有区域高亮
  regions.forEach(reg => reg.element.setAttribute("fill", "transparent"));
  
  // 检测并高亮当前区域
  regions.forEach(reg => {
    if (svg.isPointInPath(reg.path, svgPoint.x, svgPoint.y)) {
      reg.element.setAttribute("fill", "rgba(255, 165, 0, 0.3)");
    }
  });
});

// 点击着色
svg.addEventListener("click", (e) => {
  const point = svg.createSVGPoint();
  point.x = e.clientX;
  point.y = e.clientY;
  const svgPoint = point.matrixTransform(svg.getScreenCTM().inverse());
  
  regions.forEach(reg => {
    if (svg.isPointInPath(reg.path, svgPoint.x, svgPoint.y)) {
      reg.element.setAttribute("fill", "#ff6b6b"); // 可替换为自定义颜色
    }
  });
});

关键优化点

  • 性能:利用网格对称性预生成区域,避免实时相交计算的性能损耗
  • 精度:极坐标生成路径时使用弧度转换,保证区域边界与原网格完全对齐
  • 交互体验:透明填充的区域元素不会遮挡原网格,同时能准确捕获鼠标事件

内容的提问来源于stack exchange,提问作者ibrahim Abdelmonem

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 19:50:58