在前三篇文章中,我们先后学习了矩形、圆形和椭圆这三种基础图形。它们简单、直观,适合用来搭建规整的界面元素。但如果你需要绘制任意形状——一条蜿蜒的河流、一颗跳动的心脏、一个品牌的 Logo,或者一条平滑的数据趋势线——矩形和圆形就无能为力了。

这时,你需要的是 SVG 家族中的“瑞士军刀”——<path> 元素。

<path> 是 SVG 中最通用、最强大的图形元素。官方文档明确写道:“所有基本形状都可以用 path 元素创建。”换句话说,你学过的 <rect>、<circle>、<ellipse>,本质上都只是 <path> 的“特化版本”。

但强大的背后是复杂。<path> 的核心——d 属性——是一个由命令字母和数字组成的字符串,看起来像天书一样晦涩难懂。很多开发者看到 d="M10,30 A20,20 0,0,1 50,30 ..." 就望而却步了。

本文的目标,就是帮你彻底读懂这段“天书”。我们将从 SVGPathElement 接口入手,逐条拆解路径命令的含义,并通过 JavaScript 动态操控路径,让你真正“征服”这个强大的元素。

本文定位:本篇是“征服 SVGPathElement”的上半部分,聚焦于 SVGPathElement 接口概览和直线类路径命令。曲线命令(贝塞尔曲线、圆弧)将在下篇中深入讲解。

一、SVGPathElement:路径元素的 DOM 接口

在开始写路径命令之前,我们先来看看 <path> 元素在 JavaScript 中对应的接口——SVGPathElement。

1.1 继承体系回顾

回顾一下我们在第 2 篇中建立的继承链:

EventTarget → Node → Element → SVGElement → SVGGraphicsElement → SVGGeometryElement → SVGPathElement

SVGPathElement 直接继承自 SVGGeometryElement。这意味着它天然拥有所有几何图形的“超能力”:

  • getTotalLength():获取路径的总长度。

  • getPointAtLength(distance):获取路径上指定距离处的坐标点。

  • getBBox():获取路径的边界框。

  • isPointInFill() / isPointInStroke():判断点是否在填充/描边区域内。

1.2 实例属性

SVGPathElement 自身只定义了一个只读属性:

属性

类型

说明

pathLength

SVGAnimatedNumber

对应 <path> 标签上的 pathLength 属性,允许作者为路径指定一个总长度(以用户单位计)

这个属性的用途比较特殊——它允许你“告诉”浏览器路径的总长度,从而影响虚线等基于长度的渲染计算。在大多数日常开发中,你更常用的是从父级继承来的 getTotalLength() 方法,它会由浏览器自动计算路径的真实长度。

1.3 实例方法

SVGPathElement 自身定义了以下方法:

方法

说明

状态

getTotalLength()

返回浏览器计算出的路径总长度(用户单位)

标准

getPointAtLength(distance)

回路径上指定距离处的点(DOMPoint)

标准

getPathData()

返回路径数据的段序列,可选择标准化

实验性

setPathData(pathData)

将路径段序列设置为新的路径数据

实验性

getPathSegmentAtLength(distance)

返回路径上指定距离处的段

实验性

重要提示:在 SVG 2 中,getTotalLength() 和 getPointAtLength() 被移动到了父接口 SVGGeometryElement 中。但由于 SVGPathElement 继承自 SVGGeometryElement,所以你在 SVGPathElement 的实例上仍然可以调用这两个方法——它们只是“祖传”的能力而已。

1.4 已废弃的方法(了解即可)

在 SVG 1.1 时代,SVGPathElement 上有一系列 createSVGPathSeg* 方法和 getPathSegAtLength() 方法。这些在 SVG 2 中已被移除。如果你在老旧代码中看到它们,知道是历史遗留即可,不要在新项目中使用。

二、d 属性:路径的“灵魂”

<path> 元素最核心的属性就是 d(data 的缩写)。它定义了路径的实际形状。

d 属性的值是一个路径命令列表——每个命令由一个命令字母和若干数字参数组成。浏览器按照命令列表的顺序,从起点开始一笔一画地绘制出完整的图形。

2.1 命令字母的大小写含义

路径命令的字母有大写和小写两种形式,含义截然不同:

形式

含义

大写字母(如 M、L、C)

绝对坐标——参数是相对于整个 SVG 坐标系的原点

小写字母(如 m、l、c)

相对坐标——参数是相对于“当前点”的偏移量

记忆技巧:大写 = Absolute(绝对),小写 = relative(相对)。你可以在同一个 d 属性中混用大小写命令。

2.2 当前点(Current Point)

理解路径的关键是理解 “当前点” 的概念。浏览器在解析路径命令时,会始终维护一个“当前点”的位置:

  • 初始状态下,当前点在原点 (0, 0)。

  • 每执行一个绘图命令,当前点会更新为该命令的终点。

  • 后续命令的起点,就是上一个命令的终点。

这个“当前点”就像一支画笔的笔尖——你告诉它“移动到这里”“画到那里”,它就会沿着你指定的轨迹移动。

三、直线类命令

我们先从最简单的命令开始——所有画直线的命令。它们是构建复杂路径的基石。

3.1 M / m:移动到(Move To)

M 命令的作用是将“笔尖”移动到指定位置,但不画任何东西。

命令

参数

说明

M x y

绝对坐标 (x, y)

移动到绝对位置

m dx dy

相对偏移 (dx, dy)

从当前点移动 dx、dy

为什么需要 M?

因为路径的起点必须被明确指定。没有 M 命令,浏览器就不知道从哪里开始画。

示例:

d="M 100 50"

这条命令只是把笔尖移动到 (100, 50),不画任何东西。后续的命令将从这里开始绘制。

注意:在同一个 d 属性中,如果出现多个 M 命令,就表示绘制多条不相连的子路径。比如 d="M 10 10 L 50 10 M 10 30 L 50 30" 会画出两条分离的横线。

3.2 L / l:画直线到(Line To)

L 命令从当前点画一条直线到指定位置。

命令

参数

说明

L x y

绝对坐标 (x, y)

从当前点画直线到绝对位置

l dx dy

相对偏移 (dx, dy)

从当前点画直线到偏移后的位置

示例:画一个三角形

<path d="M 50 10 L 90 90 L 10 90 Z" fill="blue" />

解释:

  1. M 50 10:将笔尖移动到 (50, 10)

  2. L 90 90:从 (50, 10) 画直线到 (90, 90)

  3. L 10 90:从 (90, 90) 画直线到 (10, 90)

  4. Z:闭合路径,从 (10, 90) 画直线回到起点 (50, 10)

3.3 H / h:水平线到(Horizontal Line To)

H 命令画一条水平直线——只改变 x 坐标,y 坐标不变。

命令

参数

说明

H x

绝对 x 坐标

从当前点画水平线到 x 位置

h dx

相对偏移

从当前点画水平线,x 增加 dx

示例:

d="M 10 50 H 100"

解释:从 (10, 50) 画一条水平线到 (100, 50)。

3.4 V / v:垂直线到(Vertical Line To)

V 命令画一条垂直直线——只改变 y 坐标,x 坐标不变。

命令

参数

说明

V y

绝对 y 坐标

从当前点画垂直线到 y 位置

v dy

相对偏移

从当前点画垂直线,y 增加 dy

示例:

d="M 50 10 V 90"

解释:从 (50, 10) 画一条垂直线到 (50, 90)。

3.5 Z / z:闭合路径(Close Path)

Z 命令从当前点画一条直线回到路径的起点(即最近一个 M 或 m 命令的位置)。

命令

参数

说明

Z 或 z

无

闭合当前子路径

Z 和 z 的效果完全相同——大小写没有区别。

示例:用 H 和 V 画一个矩形

<path d="M 20 20 H 120 V 80 H 20 Z" fill="red" />

解释:

  1. M 20 20:移动到左上角

  2. H 120:画顶边到 (120, 20)

  3. V 80:画右边到 (120, 80)

  4. H 20:画底边到 (20, 80)

  5. Z:闭合,画左边回到 (20, 20)

四、通过 JavaScript 操作路径

理解了路径命令的语法之后,我们来看看如何用 JavaScript 动态操控 <path> 元素。

4.1 读取和修改 d 属性

最直接的方式是使用 getAttribute 和 setAttribute:

<svg viewBox="0 0 200 200">
  <path id="myPath" d="M 20 20 L 180 20 L 180 180 L 20 180 Z" fill="lightblue" stroke="blue" />
</svg>

const path = document.getElementById('myPath');

// 读取当前的 d 属性值
const currentD = path.getAttribute('d');
console.log(currentD);  // "M 20 20 L 180 20 L 180 180 L 20 180 Z"

// 修改 d 属性——画一个三角形
path.setAttribute('d', 'M 100 10 L 190 90 L 10 90 Z');

常见陷阱:SVGPathElement 上 没有 d 这个实例属性可以直接读写。你可能会尝试 path.d = '...',但这不会生效。必须使用 setAttribute('d', ...)。

4.2 用 getTotalLength() 获取路径长度

getTotalLength() 返回浏览器计算出的路径总长度,以用户单位计。这个值在实现路径动画时非常关键。

const length = path.getTotalLength();
console.log(`路径总长度: ${length}`);
4.3 用 getPointAtLength() 获取路径上的点

getPointAtLength(distance) 返回路径上指定距离处的坐标点,类型为 DOMPoint。

// 获取路径中点位置的坐标
const midLength = path.getTotalLength() / 2;
const midPoint = path.getPointAtLength(midLength);
console.log(`中点坐标: (${midPoint.x}, ${midPoint.y})`);

这个方法的典型应用场景包括:

  • 路径跟随动画:让一个物体沿着路径移动。

  • 标注定位:在路径的特定位置添加文字或标记。

  • 交互反馈:在鼠标悬停位置显示路径上的信息。

4.4 综合示例:动态绘制并分析路径

下面是一个完整的示例,演示如何用 JavaScript 动态创建路径、读取其长度,并获取中点坐标:

<svg viewBox="0 0 400 300" id="canvas">
  <path id="dynamicPath" fill="none" stroke="#E67E22" stroke-width="3" />
  <!-- 用来标记中点的圆 -->
  <circle id="midMarker" r="6" fill="red" />
</svg>

<button id="drawBtn">绘制路径</button>
<button id="infoBtn">显示信息</button>
<div id="info"></div>

const path = document.getElementById('dynamicPath');
const marker = document.getElementById('midMarker');
const info = document.getElementById('info');

// 点击按钮绘制一条折线路径
document.getElementById('drawBtn').addEventListener('click', () => {
  // 使用相对坐标和绝对坐标混合构建路径
  const d = 'M 50 150 L 150 50 L 250 150 L 350 50 L 350 200';
  path.setAttribute('d', d);
  info.textContent = '路径已绘制!点击"显示信息"查看详情。';
});

// 点击按钮显示路径信息
document.getElementById('infoBtn').addEventListener('click', () => {
  try {
    const totalLength = path.getTotalLength();
    const midPoint = path.getPointAtLength(totalLength / 2);
    
    // 将标记移动到中点位置
    marker.setAttribute('cx', midPoint.x);
    marker.setAttribute('cy', midPoint.y);
    marker.setAttribute('r', '6');
    marker.setAttribute('fill', 'red');
    
    info.innerHTML = `
      <strong>路径分析结果:</strong><br />
      总长度:${totalLength.toFixed(2)} 用户单位<br />
      中点坐标:(${midPoint.x.toFixed(2)}, ${midPoint.y.toFixed(2)})<br />
      路径命令:${path.getAttribute('d')}
    `;
  } catch (e) {
    info.textContent = '请先绘制路径!';
  }
});

五、d 作为 CSS 属性(前沿知识点)

在传统 SVG 中,d 是 <path> 元素的一个标签属性。但在现代 Web 标准中,d 也可以作为 CSS 属性使用。

这意味着你可以通过 CSS 来改变路径的形状,甚至实现路径的形变动画:

/* 悬停时改变路径形状 */
#svg_css_ex1:hover path {
  d: path("M10,30 A20,20 0,0,1 50,30 A20,20 0,0,1 90,30 Q90,60 50,90 Q10,60 10,30 z M5,5 L90,90");
}

应用场景:使用 CSS d 属性配合 transition,可以实现路径形状的平滑过渡动画——这是 SVG 1.1 时代需要用 SMIL 或 JavaScript 才能完成的效果。

不过需要注意的是,CSS d 属性只适用于 SVG <path> 元素,不适用于其他 SVG 元素或 HTML 元素。

六、常见错误与避坑指南

错误一:忘记 M 命令
<!-- 这样写是错的!没有指定起点 -->
<path d="L 100 100" />

正确做法:每个路径必须以 M 或 m 开头。

错误二:混用绝对和相对坐标时计算错误
<!-- 从 (50, 50) 出发,想画到 (100, 80) -->
<path d="M 50 50 l 50 30" />  <!-- 正确:相对偏移 -->
<path d="M 50 50 L 100 80" /> <!-- 正确:绝对坐标 -->
<path d="M 50 50 l 100 80" /> <!-- 错误:实际到了 (150, 130) -->

正确做法:使用相对坐标时,始终记得参数是偏移量而不是绝对位置。

错误三:通过 path.d 直接赋值
// 这样写不会生效!
path.d = 'M 10 10 L 90 90';

正确做法:使用 setAttribute('d', ...)。

错误四:忘记 Z 导致路径不闭合
<!-- 三角形缺了一条边 -->
<path d="M 50 10 L 90 90 L 10 90" />

虽然视觉上可能看起来是三角形(如果 fill 生效),但路径实际上没有闭合——最后一条边(从 (10, 90) 回到 (50, 10))是不存在的。如果设置了 fill,浏览器会自动填充封闭区域,但 stroke 会暴露出缺失的边。

正确做法:如果需要闭合路径,在末尾加上 Z。

七、本章总结

概念

要点

SVGPathElement

<path>的 DOM 接口,继承自SVGGeometryElement

d属性

路径的核心,由命令字母 + 数字参数组成

大写 vs 小写

大写 = 绝对坐标,小写 = 相对坐标

当前点

浏览器维护的“笔尖”位置,每个命令的终点成为下个命令的起点

M / m

移动到指定位置(不画线)

L / l

画直线到指定位置

H / h

画水平线

V / v

画垂直线

Z / z

闭合路径——回到起点

getTotalLength()

获取路径总长度

getPointAtLength()

获取路径上指定距离处的坐标点

CSS d 属性

前沿特性,支持用 CSS 控制路径形状

三个核心认知:

  1. 路径的本质是一系列绘图指令——浏览器按照指令列表,从当前点开始逐条执行,最终形成完整的图形。

  2. SVGPathElement 的能力来自继承——getTotalLength() 和 getPointAtLength() 实际上是 SVGGeometryElement 赋予的。

  3. 操作路径用 setAttribute——SVGPathElement 没有直接的 d 属性,必须通过 setAttribute('d', ...) 来修改。

下篇预告

在下一篇文章中,我们将进入路径命令的 高阶领域——曲线。你将学会:

  • 二次贝塞尔曲线(Q / q):用一个控制点定义的平滑曲线。

  • 三次贝塞尔曲线(C / c):用两个控制点定义的最灵活曲线。

  • 平滑曲线(S / s、T / t):让曲线过渡更自然的快捷命令。

  • 圆弧(A / a):用半径和角度定义的椭圆弧。

  • 综合实战:用路径绘制一个品牌 Logo。

互动话题:尝试用本文学到的直线命令,在 SVG 中画一个你喜欢的几何图形(比如五角星、房子、箭头)。欢迎在评论区分享你的 d 属性代码和效果截图!