Skip to content

路径: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-rulenonzero(默认,非零环绕数)vs evenodd(奇偶射线法)——自交路径/镂空(甜甜圈)图形结果不同;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/lx y画直线到目标点
H/hx水平线(只给 x)
V/vy垂直线(只给 y)
C/cx1 y1 x2 y2 x y三次贝塞尔:起点控制点 + 终点控制点 + 终点
S/sx2 y2 x y平滑三次:省略第一控制点,自动反射推断
Q/qx1 y1 x y二次贝塞尔:单控制点 + 终点
T/tx 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——七个参数逐个说清:

  1. rx / ry:椭圆两个半径(画圆弧则相等)。半径太小连不到终点时,浏览器会自动等比放大半径,而不是报错——「为什么我的半径没生效」的来源。
  2. x-axis-rotation:椭圆自身 x 轴相对水平方向的旋转角(度),画正圆时无意义。
  3. large-arc-flag1大弧(圆心角 ≥180°),0 走小弧。
  4. sweep-flag1 沿正角度方向画(在 y 向下的屏幕坐标系里呈顺时针),0 反向。
  5. x y:弧线终点。

起点隐含为当前点。给定两点 + 两半径,平面上存在 4 条候选弧(两个椭圆 × 各两段),large-arc-flagsweep-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 测长。

这三件套在填充描边与渐变的描边动画一节会立刻用上。