你以为 clip-path 只能靠 SVG?今天 CSS shape() 把这件事彻底原生化了

设计师给了一张带波浪底部的人物剪影图,要你在 Hero 区域实现。配上去之后,在设计师屏幕上完美,但你拿过来一打开——移动端变形了。

这是每一个用过 clip-path: path() 的前端工程师都踩过的坑:SVG path 用的是固定像素坐标,元素一大就崩,一缩也崩,没有任何响应式可言。

shape() 函数解决了这个问题。

旧方案的三重局限

在 shape() 之前,你想给图片加一个曲线裁切,只有三种路,每条都有硬伤。

polygon():接受百分比,是响应式的,但只支持直线段。想画波浪?需要几十个点凑,元素一大这些点之间的距离就全变了,波浪变成了锯齿。

path():接受真实 SVG 贝塞尔曲线命令,能画曲线,但坐标全是固定像素。一个在 800px 宽度下画出来的 path,缩到 375px 就完全走形。

SVG mask:能响应式,但做不到「形状等比缩放但曲线厚度不变」。而且你需要多一个 SVG 文件、一个 viewBox、一段 url(#…) 引用——设计师动一下文件,所有东西都要重新对。

实际项目中,这三个方案的最终结果往往是:要么接一个 resize 监听器让 JS 实时重算 path(性能杀手),要么直接告诉产品和设计师「移动端做不到」,或者发版前一天设计师发现「波浪怎么变形了」。

shape() 是什么

shape() 是 clip-path 的新函数,2026 年 2 月进入 Baseline。

它把 SVG path 的绘图命令直接搬进了 CSS,但关键区别在于:所有坐标都接受 CSS 单位,百分比、rem、calc()、甚至混用。

语法长这样

.hero {
  clip-path: shape(
    from 0% 0%,
    line to 100% 0%,
    line to 100% 80%,
    curve to 0% 80% via 50% 105%,
    close
  );
}

对比一下 path() 版本的固定像素:

.hero {
  /* 固定像素,800px 宽度下画,缩放必崩 */
  clip-path: path("M 0 0 L 800 0 L 800 360 Q 400 420 0 360 Z");
}

shape() 版本的波浪底部用百分比坐标,元素有多宽,曲线就自动按比例适配——不需要任何 JS。

支持的命令

shape() 的命令和 SVG path 类似,但语法更接近 CSS:

  • from X% Y%:起点
  • line to X% Y%:直线
  • hline to X% / vline to Y%:水平/垂直直线
  • curve to X% Y% via X1% Y1% [, X2% Y2%]:二次/三次贝塞尔曲线
  • arc to X% Y% of R%:椭圆弧
  • smooth to X% Y% via X1% Y1%:光滑曲线
  • close:闭合路径

每个命令后面可以跟 by(相对坐标)或 to(绝对坐标)。

实战:波浪 Hero 区域

这是生产环境中 shape() 最常见的用法——给区块底部加一个波浪分隔线,替代以前需要切图或者用伪元素叠 PNG 的做法:

.section {
  clip-path: shape(
    from 0% 0%,
    line to 100% 0%,
    line to 100% 85%,
    curve to 0% 85% via 25% 100%, 75% 70%,
    close
  );
}

85% 处开始画曲线,左边 25% 处开始向上拱、右边 75% 处回落——这个波浪在任何宽度下形状比例都保持一致。

混用单位:固定厚度曲线

shape() 最厉害的能力:不同坐标轴可以用不同单位,浏览器会分别处理。

想让波浪曲线在元素缩放时,曲线本身的「拱高」保持固定像素值,而不是跟着元素等比缩放:

.hero {
  clip-path: shape(
    from 0% 20px,
    curve to 100% 20px via 25% 0% / 75% 40px,
    vline to calc(100% - 20px),
    curve to 0% calc(100% - 20px) via 75% 100% / 25% calc(100% - 40px),
    close
  );
}

20px 的曲线拱高在所有尺寸下都保持不变,宽度方向用百分比响应式——这个能力在 path() 和 SVG mask 里都做不到。

可以动画了

path() 的裁切区域是不能做 CSS transition 的。两个不同的 path 值之间,浏览器直接跳帧。

shape() 改变了这一点——只要两个 shape 的顶点数相同,就可以做平滑过渡:

.card {
  clip-path: shape(
    from 0% 0%,
    line to 100% 0%,
    line to 100% 100%,
    line to 0% 100%,
    close
  );
  transition: clip-path 0.4s ease;
}

.card:hover {
  clip-path: shape(
    from 0% 0%,
    curve to 100% 0% via 50% -10%,
    line to 100% 100%,
    curve to 0% 100% via 50% 110%,
    close
  );
}

卡片悬停时,从直角矩形变成圆角卡片形状,平滑过渡——这个效果以前只能靠 SVG SMIL 或者 JS 动画库来实现。

浏览器支持

Chrome 135+、Firefox 148+、Safari 18.4+、Edge 135+,Baseline 2026。覆盖约 85-96% 全球用户。

渐进增强写法:

.hero {
  /* 旧浏览器回退:斜切矩形 */
  clip-path: polygon(0 0, 100% 0, 100% 85%, 0 100%);
}

@supports (clip-path: shape(from 0% 0%, line to 100% 0%, close)) {
  .hero {
    clip-path: shape(
      from 0% 0%,
      line to 100% 0%,
      line to 100% 80%,
      curve to 0% 80% via 50% 105%,
      close
    );
  }
}

三个坑

1. Firefox 148 以下不支持:Firefox 在 2026 年初才完整支持 shape(),此前需要 @supports 检测。

2. 不能夸类型过渡:circle() → shape() 这种跨类型过渡不会插值,浏览器直接跳帧。要做形态变换动画,两端都用 shape() 并且顶点数相同。

3. clip-path 裁切不影响 focus 顺序:被裁切掉的区域里的按钮和链接仍然可以被 tab 到,也会被屏幕阅读器读到。裁切交互区域时要同步设置 aria-hidden 或者 inert。

三步下一步

  1. 查项目里有没有 path():grep “clip-path.*path(” 找到所有固定像素的裁切,这些是 shape() 的直接替换目标
  2. 从波浪 Hero 切入:找一个有斜切底边的 Hero 或 Section,把 polygon 替换成 shape(),用 curve 命令还原曲线
  3. 给 shape() 加 @supports:写完 shape() 版本后,外层套一层 @supports 包裹旧 polygon 版本做回退

shape() 解决的不只是「画曲线」的问题,而是把 CSS 的响应式能力真正延伸到了图形裁切领域。在此之前,前端工程师要用 SVG、要用 resize 监听器、要在设计师和移动端之间做妥协——现在,一个函数把这些全收进去了。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 11 阅读