路径:d 命令全解与 fill-rule
基于 SVG 1.1 / SVG 2 CR(2026 浏览器现状)· 核于 2026-07
速查
- 大小写规则:大写 = 绝对坐标;小写 = 相对当前点位移(Z/z 无参数、不分大小写)。
- d 命令全表:
M/m x y:移动画笔(不画线);m 之后的后续坐标对按 L/l 处理。L/l x y:直线;H/h x:水平线;V/v y:垂直线。C/c x1 y1 x2 y2 x y:三次贝塞尔(起点控制点 + 终点控制点 + 终点)。S/s x2 y2 x y:平滑三次——第一控制点 = 前一条 C/S 第二控制点关于当前点的中心对称点;前面不是 C/S 时取当前点。Q/q x1 y1 x y:二次贝塞尔(单控制点 + 终点)。T/t x y:平滑二次——控制点由前一条 Q/T 反射推断;单独使用退化为直线。A/a rx ry x-axis-rotation large-arc-flag sweep-flag x y:椭圆弧(7 参数)。Z/z:直线连回本子路径起点闭合。
- A 命令 7 参数(最高频考点):两半径 rx/ry → 椭圆 x 轴旋转角(度)→ large-arc-flag(1 大弧 ≥180° / 0 小弧)→ sweep-flag(1 正角度方向,屏幕坐标系里呈顺时针 / 0 反向)→ 终点 x y。起点隐含为当前点;两点 + 两半径确定 4 条候选弧,两个 flag 四选一。
- A 命令的坑:半径太小会被自动放大到能连接两点(不报错);紧凑语法里 flag 可与后续数字连写(如
0 0140 20)极难读——手写全用空格。 - fill-rule:
nonzero(默认,非零环绕数)vsevenodd(奇偶射线法)——自交路径/镂空(甜甜圈)图形结果不同;clip-rule是 clipPath 里的对应物。 - 路径测长:JS
path.getTotalLength()取真实长度;getPointAtLength(len)沿路径取点;pathLength="100"属性把任意路径长度归一化(描边动画免测长)。 - 所有基本形状都能用 path 表达;path 是 SVG 最强也最常见的元素(图标导出基本全是 path)。
一、d 属性与大小写语义
<path d="..."> 用一串单字母命令 + 参数描述任意形状。第一条规则贯穿所有命令:
- 大写命令 = 绝对坐标:参数是用户坐标系里的绝对位置。
- 小写命令 = 相对位移:参数是相对「当前点」的偏移量。
xml
<!-- 两种写法画同一个三角形 -->
<path d="M 10 10 L 90 10 L 50 80 Z"/>
<path d="m 10 10 l 80 0 l -40 70 z"/>M/m 移动画笔不画线,每条路径必以它开头;m 后若跟多组坐标,第 2 组起按 l 处理(M 同理按 L)。Z/z 用直线连回本子路径起点闭合,无参数、不分大小写。一条 d 里可以有多个 M 开启多条子路径——这正是镂空图形的画法(配 fill-rule,见下)。
二、直线与贝塞尔曲线
| 命令 | 参数 | 语义 |
|---|---|---|
| L/l | x y | 画直线到目标点 |
| H/h | x | 水平线(只给 x) |
| V/v | y | 垂直线(只给 y) |
| C/c | x1 y1 x2 y2 x y | 三次贝塞尔:起点控制点 + 终点控制点 + 终点 |
| S/s | x2 y2 x y | 平滑三次:省略第一控制点,自动反射推断 |
| Q/q | x1 y1 x y | 二次贝塞尔:单控制点 + 终点 |
| T/t | x y | 平滑二次:控制点自动反射推断 |
平滑命令的反射规则是面试细节题:
S的第一控制点 = 前一条 C/S 命令第二控制点关于当前点的中心对称点——保证连接处切线连续、曲线光滑。若前一条命令不是 C/S,则第一控制点退化为当前点。T同理反射前一条 Q/T 的控制点;单独使用(前面没有 Q/T)时退化为直线。
xml
<!-- C 接 S:S 自动镜像前一段的控制点,接缝光滑 -->
<path d="M 10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80"
fill="none" stroke="black"/>
<!-- Q 接 T:波浪线只需一个显式控制点 -->
<path d="M 10 80 Q 52 10, 95 80 T 180 80" fill="none" stroke="black"/>三、A 命令:椭圆弧七参数(最高频考点)
A rx ry x-axis-rotation large-arc-flag sweep-flag x y——七个参数逐个说清:
- rx / ry:椭圆两个半径(画圆弧则相等)。半径太小连不到终点时,浏览器会自动等比放大半径,而不是报错——「为什么我的半径没生效」的来源。
- x-axis-rotation:椭圆自身 x 轴相对水平方向的旋转角(度),画正圆时无意义。
- large-arc-flag:
1走大弧(圆心角 ≥180°),0走小弧。 - sweep-flag:
1沿正角度方向画(在 y 向下的屏幕坐标系里呈顺时针),0反向。 - x y:弧线终点。
起点隐含为当前点。给定两点 + 两半径,平面上存在 4 条候选弧(两个椭圆 × 各两段),large-arc-flag 与 sweep-flag 的组合正好从中四选一——这是理解这两个 flag 的正确心智模型。
xml
<!-- 四分之一圆:从 (100,50) 顺时针小弧到 (50,100),圆心 (50,50) r=50 -->
<path d="M 100 50 A 50 50 0 0 1 50 100" fill="none" stroke="black"/>
<!-- 饼图扇形:圆心 → 直线到弧起点 → 弧 → Z 闭合回圆心 -->
<path d="M 50 50 L 100 50 A 50 50 0 0 1 50 100 Z" fill="orange"/>语法宽松是坑:d 里的分隔符(空格/逗号)大多可省,flag 又只能是 0/1,于是 A20 20 0 0120 20 这种紧凑写法是合法的(flag 与后续坐标连写)——机器能解析,人会读错。手写路径一律用空格分隔所有参数。
四、fill-rule:nonzero vs evenodd
当路径自交或包含多条子路径(如「甜甜圈」)时,「哪里算内部」需要一条判定规则,即 fill-rule:
- nonzero(默认,非零环绕数):从待判定点向外引射线,路径段从左往右穿过计 +1、从右往左穿过计 −1;总和非零则在内部。结果受子路径绘制方向影响——内外圈同向时「洞」会被填上。
- evenodd(奇偶规则):只数射线与路径的交点个数,奇数在内、偶数在外——与方向无关,画镂空图形更省心。
xml
<!-- 同一组同向双圆:nonzero 填成实心大饼,evenodd 出甜甜圈 -->
<path fill-rule="nonzero" fill="teal"
d="M 50 50 m -40 0 a 40 40 0 1 0 80 0 a 40 40 0 1 0 -80 0
M 50 50 m -20 0 a 20 20 0 1 0 40 0 a 20 20 0 1 0 -40 0"/>
<path fill-rule="evenodd" fill="teal" transform="translate(120 0)"
d="M 50 50 m -40 0 a 40 40 0 1 0 80 0 a 40 40 0 1 0 -80 0
M 50 50 m -20 0 a 20 20 0 1 0 40 0 a 20 20 0 1 0 -40 0"/>在 clipPath 内部对应的属性叫 clip-rule。图标库导出的路径偶尔会因为环绕方向问题在默认 nonzero 下「洞消失」,切 evenodd 或反转子路径方向即可修复。
五、路径测长与取点
路径不仅能画,还能被测量——这是描边动画与「沿路径运动」的地基:
path.getTotalLength():返回路径总长(用户单位),描边动画拿它当 dasharray。path.getPointAtLength(len):返回路径上距起点 len 处的坐标点,可做自定义「沿路径取样」。pathLength="100"属性:告诉浏览器「把这条路径的长度当作 100 来换算」——任意路径归一化成固定长度,纯 CSS 描边动画从此不需要 JS 测长。
这三件套在填充描边与渐变的描边动画一节会立刻用上。