CSS shape() 函数

形状是图形设计的重要方面。shape() 函数提供了一种新的方式来编写形状,这些形状可以根据元素的大小,以混合比例进行适应和缩放。

多年来,CSS 已经赋予 Web 开发者使用形状进行裁剪和动画的能力。形状最常见的用法是在clip-path 属性中。这会在元素布局完成后应用裁剪,允许你作为 Web 开发者进行一些操作,例如将边缘修剪成装饰性的波浪形状。这可以产生与遮罩相似的效果,但渲染效率比遮罩更高。形状的另一个用例是在offset-path 属性中,它允许你沿着路径对某个事物进行动画处理。最后,shape-outside 提供了一种使文本围绕非矩形形状环绕的方式(但目前仅支持部分形状类型)。

CSS 提供了一组形状,可以与这些属性配合使用。(在 Web 标准中,这些形状在基本形状中得到了完整定义。)有多种方式可以描述矩形,以及 circle()ellipse()polygon()(一组直线段)以及采用 SVG 样式路径的 path() 函数。

例如,考虑这个简单的箭头形状

SVG arrow shape with right angle pointing right and slight right-set curve

生成此形状的 SVG 为

<svg viewBox="0 0 150 100" xmlns="http://www.w3.org/2000/svg">
  <path fill="black" d="M0 0 L 100 0 L 150 50 L 100 100 L 0 100 Q 50 50 0 0 z " />
</svg>

我们稍后将分解此路径。现在,让我们将该路径应用于 clip-path 中的 HTML 元素

.clipped {
    width: 150px;
    height: 100px;
    box-sizing: border-box;
    background-color: blue;
    border: 10px solid orange;
    clip-path: path("M0 0 L 100 0 L 150 50 L 100 100 L 0 100 Q 50 50 0 0 z");
}

它产生如下效果

Unique arrow shape with blue rectangle and orange border where the tip of the right angle shows a portion of the orange border

请注意我如何添加了边框,以便您可以看到 clip-path 是如何裁剪元素部分的。

但是,如果我们现在改变元素的尺寸会发生什么呢?例如,如果我们要一个更长的箭头呢?

.clipped {
    width: 200px;
    ...
}

遗憾的是,我们得到了相同的形状,只是箭头的尖端不再显示边框。

Unique arrow shape with blue rectangle and orange border where the tip of the right angle does not show the orange border

这意味着在 clip-path 中使用 path() 无法实现响应式;你无法编写 CSS 规则,使路径适应元素的大小。这就是新的 shape() 函数发挥作用的地方。

shape()

新的 shape() 函数通过允许你使用已经熟悉的 CSS 关键字和单位来指定路径,包括 calc() 和 CSS 变量的全部功能,直接解决了这个响应性问题,而且它更具可读性。

让我们回到并分解那个看起来像一串难以理解的字母和数字的 SVG 路径:“M0 0 L 100 0 L 150 50 L 100 100 L 0 100 Q 50 50 0 0 z”。我们可以将其分解为一系列“命令”,这些命令描述了路径的每个部分

M 0 0 移动到 0, 0
L 100 0 画线到 100, 0
L 150 50 画线到 150, 50
L 100 100 画线到 100, 100
L 0 100 画线到 0, 100
Q 50 50 0 0 二次贝塞尔曲线,控制点位于 50, 50,结束于 0, 0
z 闭合路径

我们可以使用 CSS 规范中指定的名称将此路径转录成一个形状;请注意第一个“move”命令在 shape 中如何变为“from”

clip-path: shape(from top left,
    line to 100px top,
    line to 150px 50%,
    line to 100px bottom,
    line to bottom left,
    curve to top left with 50px 50%,
    close);

请注意,当点在绝对坐标中时,我们可以使用“top”和“bottom left”等关键字,并且我们可以使用百分比。

这给了我们与上面相同的结果,但我们还没有使其完全响应式。我们的下一步是使形状随元素的宽度水平拉伸,但将高度固定为 100px。让我们用一点数学来实现这一点。为了简化,我们将元素的期望高度放入一个变量中

.responsive-clip {
    --height: 100px;
    height: var(--height);
    ...
}

我们还将定义半高值,用于计算右侧的箭头形状,以及二次曲线的控制点。

    --half-height: calc(var(--height) / 2);

形状的响应式部分可以用百分比来表示。x 坐标中使用的百分比是相对于元素宽度的,而 y 坐标中使用的百分比是相对于元素高度的

clip-path: shape(from top left,
    line to calc(100% - var(--half-height)) 0%,
    line to 100% var(--half-height),
    line to calc(100% - var(--half-height)) 100%,
    line to left bottom,
    curve to left top with var(--half-height) var(--half-height),
    close);

现在我们有了一个可以与元素一样长的剪辑!

Unique right pointing arrow that stretches maintaining the 90 degree angle of the arrow point while still showing the orange border at the tip

使此形状适应元素的高度更为棘手,因为我们需要能够在水平轴上的值中引用高度。这是我们可以使用容器查询 (Container Queries) 实现的。首先,让我们在 HTML 标记中创建一个容器

<div class="responsive-clip-container">
  <div class="responsive-clip"></div>
</div>

一旦我们定义了容器并将元素的尺寸移到那里,我们现在就可以将 var(--half-height) 替换为 50cqh

.responsive-clip-container {
  width: 100%;
  aspect-ratio: 5 / 1;
  container-type: size;
}

.responsive-clip {
  height: 100%;
  background: blue;
  border: 10px solid orange;
  box-sizing: border-box;
  clip-path: shape(from top left,
    line to calc(100% - 50cqh) 0%,
    line to 100% 50cqh,
    line to calc(100% - 50cqh) 100%,
    line to bottom left,
    curve to top left with 50cqh 50cqh,
    close);
}

让我们为容器添加一个调整器,以便您可以试用响应性!

.responsive-clip-container {
  resize: both;
  overflow: clip;
}

在 Safari 18.4 或其他支持的浏览器中尝试此演示

查看 Jen Simmons (@jensimmons) 在
CodePen 上创作的 Pen – 演示 3

CodePen上。

调整箭头大小有助于真正展示 shape() 的功能。请注意曲线和 90 度角是如何保留的,这是拉伸 SVG 时无法实现的效果。

shape() 函数还有更多我们在此未提及的功能,例如相对而非绝对命令(例如 move by 10px, 2em)、其他命令如 arc,以及描述曲线控制点的各种方式。深入研究Web 标准以了解更多信息。当然,只要形状包含相同的命令列表,你就可以在形状之间进行动画,这可以产生一些非常酷的效果。

查看 Simon Fraser (@smfr) 在
CodePen 上创作的 Pen – 形状动画

CodePen上。

告诉我们您对 shape() 的看法。我们很想看看您会创造出什么!