当我第一次听说clip-path: path()
即将进入 Firefox 时,我非常兴奋。 想象一下,只需一个 HTML 元素和很少的 CSS 就能轻松地编码一个像下面这样的呼吸框,而无需 SVG 或多边形函数中的大量点列表!
这将多么有趣

我决定试一试。 我进入 CodePen,在 HTML 面板中放置了一个<div>
,以视口单位为其设置尺寸,以便它可以很好地缩放,并添加了一个background
以便我可以看到它。 然后,我访问 MDN 以查看一些使用示例…… 而我的蓬松云朵般的梦想开始破灭了!
请注意,clip-path: path()
仅在 Firefox 63-70 中有效,并且在about:config
中将layout.css.clip-path-path.enabled
标志设置为true
,以及在 Firefox 71+ 中无需启用任何标志。(来源:MDN。)
这些是我找到的示例
path('M0 200L0 110A110 90 0 0 1 240 100L 200 340z')
path('M.5 1C.5 1 0 .7 0 .3A.25 .25 1 1 1 .5 .3 .25 .25 1 1 1 1 .3C1 .7 .5 1 .5 1Z')
这些坐标是什么? 可悲的答案是**像素值**! 使用它们是因为path()
函数采用 SVG <path>
字符串作为参数,该字符串(就像 SVG d
属性在<path>
元素上的值一样)仅包含一种坐标值:无单位像素。 在 SVG 案例中,这些像素随<svg>
元素的viewBox
缩放,但它们在 CSS path()
函数内部根本不会缩放!
这意味着如果我们有一个具有clip-path
属性的path()
值的响应式元素,则该元素始终会被裁剪到相同的固定区域。 例如,考虑一个边长为35vw
的正方形.box
。 我们使用path()
函数将其裁剪成心形
clip-path: path('M256 203C150 309 150 309 44 203 15 174 15 126 44 97 73 68 121 68 150 97 179 68 227 68 256 97 285 126 285 174 256 203')
当我们实际的.box
元素的尺寸随视口变化时,这个心形保持相同的大小

path()
的问题。在 2020 年,响应式设计是标准而不是例外,这是一个坏消息。 除了我们想要裁剪的元素实际上具有固定像素大小的奇特情况外,path()
函数完全没用! 我们今天仍然最好使用实际的 SVG,甚至使用clip-path
的polygon()
近似值。 简而言之,尽管path()
已经起步,但它仍然需要改进。
Amelia Bellamy-Royds 在这里提出了两种可能性
选项 1:允许在路径数据内使用
calc()
值/单位。 这可能是在一般扩展 SVGpath
语法时完成的。选项 2:在
clip-path
声明中指定viewBox
,缩放路径以适应。
我个人更喜欢第一个选项。 第二个选项相对于使用 SVG 唯一提供的优势是,我们不必包含实际的 SVG。 也就是说,包含实际的 SVG 将始终具有更好的支持。
但是,第一个选项可以比使用 SVG 有很大的改进——至少足以证明在 HTML 元素上使用clip-path
而不是在其中包含 SVG。 让我们考虑一下这篇文章顶部的呼吸框。 使用 SVG,我们有以下标记
<svg viewBox='-75 -50 150 100'>
<path/>
</svg>
请注意,viewBox
的设置使得0,0
点正好位于中间。 这意味着我们必须使左上角的坐标(即前两个viewBox
值)等于减去一半的viewBox
尺寸(即最后两个viewBox
值)。
在 SCSS 中,我们将初始正方形框的边长($l
)设置为最小的viewBox
尺寸(即最后两个值中最小的一个)。 在我们的例子中,它是100
。
我们从正方形框的左上角开始路径。 这意味着移动到(M
)命令到该点,其坐标都等于边长的一半的负数。
然后我们向下移动到左下角。 这需要绘制一条长度等于边长($l
)且向下(在y轴的正方向上)的垂直线。 因此,我们将使用v
命令。
接下来,我们移动到右下角。 我们将绘制一条长度等于边长($l
)且向右(在x轴的正方向上)的水平线。 我们将使用h
命令来实现这一点。
移动到右上角意味着绘制另一条长度等于边长($l
)的垂直线,因此我们将再次使用v
命令——只是这次的区别在于我们沿着y轴的反方向移动,这意味着我们使用相同的坐标,但带负号。
将所有内容放在一起,我们就有了一个允许我们创建初始正方形框的 SCSS
.box {
d: path('M#{-.5*$l},#{-.5*$l} v#{$l} h#{$l} v#{-$l}');
fill: darkorange
}
生成的 CSS(其中$l
被替换为100
)如下所示
.box {
d: path('M-50,-50 v100 h100 v-100');
fill: darkorange;
}
结果可以在下面的交互式演示中看到,其中将鼠标悬停在路径数据的一部分上会突出显示生成的 SVG 中的对应部分,反之亦然
请参阅 thebabydino 在 CodePen 上的 Pen(@thebabydino)。
但是,如果我们希望侧边“呼吸”,则不能使用直线。 让我们用二次贝塞尔曲线(q
)替换它们。 终点保持不变,即沿同一条垂直线向下移动一个边长。 我们通过0,#{$l}
到达那里。
但是我们在之前需要指定的控制点呢? 我们将该点垂直放置在起点和终点之间的中间,这意味着我们向下移动到它,移动距离为到达终点距离的一半。
假设在水平方向上,我们将它放置在边长四分之一处的一个方向或另一个方向上。 如果我们希望线条突出以拓宽框或将其挤压以缩窄它,我们需要执行以下操作
d: path('M#{-.5*$l},#{-.5*$l}
q#{-.25*$l},#{.5*$l} 0,#{$l}
h#{$l}
v#{-$l}'); /* swollen box */
d: path('M#{-.5*$l},#{-.5*$l}
q#{.25*$l},#{.5*$l} 0,#{$l}
h#{$l}
v#{-$l}'); /* squished box */
这编译成以下 CSS
d: path('M-50,-50
q-25,50 0,100
h100
v-100'); /* swollen box */
d: path('M-50,-50
q25,50 0,100
h100
v-100'); /* squished box */
下面的交互式演示显示了此path
的工作原理。 您可以将鼠标悬停在路径数据组件上以查看它们在 SVG 图形上的突出显示。 您还可以切换膨胀和收缩版本。
请参阅 thebabydino 在 CodePen 上的 Pen(@thebabydino)。
这仅是左边缘。 我们也需要对右边缘执行相同的操作。 此处的区别在于我们是从右下角到右上角而不是向下(在y轴的负方向上)。 我们将控制点放置在框外以获得宽框效果,这也意味着将其放置在其端点的右侧(在x轴的正方向上)。 同时,我们将控制点放置在内部以获得窄框效果,这意味着将其放置在其端点的左侧(在x轴的负方向上)。
d: path('M#{-.5*$l},#{-.5*$l}
q#{-.25*$l},#{.5*$l} 0,#{$l}
h#{$l}
q#{.25*$l},#{-.5*$l} 0,#{-$l}'); /* swollen box */
d: path('M#{-.5*$l},#{-.5*$l}
q#{.25*$l},#{.5*$l} 0,#{$l}
h#{$l}
q#{-.25*$l},#{-.5*$l} 0,#{-$l}'); /* squished box */
上面的 SCSS 生成以下 CSS
d: path('M-50,-50
q-25,50 0,100
h100
q25,-50 0,100'); /* swollen box */
d: path('M-50,-50
q25,50 0,100
h100
q-25,-50 0,-100'); /* squished box */
请参阅 thebabydino 在 CodePen 上的 Pen(@thebabydino)。
为了获得呼吸效果,我们在膨胀状态和收缩状态之间进行动画切换
.box {
d: path('M#{-.5*$l},#{-.5*$l}
q#{-.25*$l},#{.5*$l} 0,#{$l}
h#{$l}
q#{.25*$l},#{-.5*$l} 0,#{-$l}'); /* swollen box */
animation: breathe .5s ease-in-out infinite alternate
}
@keyframes breathe {
to {
d: path('M#{-.5*$l},#{-.5*$l}
q#{.25*$l},#{.5*$l} 0,#{$l}
h#{$l}
q#{-.25*$l},#{-.5*$l} 0,#{-$l}'); /* squished box */
}
}
由于两种状态之间唯一不同的只是控制点水平差的符号(二次贝塞尔曲线q
命令后第一个数字的符号),因此我们可以使用混合宏简化操作
@mixin pdata($s: 1) {
d: path('M#{-.5*$l},#{-.5*$l}
q#{-.25*$s*$l},#{.5*$l} 0,#{$l}
h#{$l}
q#{.25*$s*$l},#{-.5*$l} 0,#{-$l}')
}
.box {
@include pdata();
animation: breathe .5s ease-in-out infinite alternate
}
@keyframes breathe { to { @include pdata(-1) } }
这几乎就是我为实际的呼吸框演示所做的工作,尽管运动稍微更谨慎。 尽管如此,这对生成的 CSS 没有任何影响——我们仍然在编译后的代码中拥有两个长而丑陋且几乎相同的路径。
但是,如果我们能够使用一个<div>
,并使用支持各种值(包括内部的calc()
值)的clip-path: path()
进行裁剪,那么我们可以将符号设置为自定义属性--sgn
,然后借助 Houdini 在-1
和1
之间对其进行动画。
div.box {
width: 40vmin; height: 20vmin;
background: darkorange;
--sgn: 1;
clip-path: path(M 25%,0%
q calc(var(--sgn)*-25%),50% 0,100%
h 50%
q calc(var(--sgn)*25%),-50% 0,-100%);
animation: breathe .5s ease-in-out infinite alternate
}
@keyframes breathe { to { --sgn: -1 } }
能够做到这一点将产生天壤之别。 我们的元素将随视口很好地缩放,我们从中裁剪出的呼吸框也是如此。 而且,最重要的是,我们不需要重复此裁剪路径以获取其两个不同版本(膨胀版本和收缩版本),因为在calc()
值内使用符号自定义属性(--sgn
)将可以解决问题。 但是就目前而言,clip-path: path()
几乎毫无用处。
我上周刚遇到这个问题,以为可以使用 clip-path: path 来处理某些事情,但即使使用 SVG,我也想不出解决方案。
我的问题是我想在底角有一个固定大小的“小东西”,并从它延伸到顶角,在一个可变高度的框内,一个“响应式大小的小东西”。 不行。
需要类似于 calc( 100% – 40px ) 的东西作为“响应式大小的小东西”的高度,但我看不到在 CSS path 或 SVG 中执行此操作的方法。
CSS 蒙版边框(也称为
webkit-mask-box-image
)非常适合此(https://css-tricks.org.cn/almanac/properties/m/mask-border/)!与
border-image
一样,您可以定义四个角、四条边甚至填充作为蒙版,并拥有所有精细控制。唯一的缺点是 Firefox 尚未支持它(所有其他重要浏览器已经支持)。
在某些情况下,可以使用不因元素尺寸而失真的 CSS 渐变的普通 CSS 蒙版图像作为后备。
为了使这种东西实用,我觉得我们需要一些在线生成器。 我可以看到一些可以帮助您首先创建 clip-path 的生成器,但没有任何可以允许动画的生成器。
太酷了!
您提出的路径语法对 CSS 语法整体而言有一些非常严重的影响。
您引用的工作示例将路径数据作为字符串,并用引号括起来。这使得该字符串成为传递给
path()
函数的 一个 参数。您的提议省略了引号,这显然有一些优势:CSS 解析器可以像通常那样解释值单位和嵌套 (calc()
) 函数(您在示例中还在字符串内使用了换行符,但这 是一个语法错误。在字符串内使用换行符是 XML 属性语法。)没有引号,路径数据就变成了 组件值(您可以称之为参数)列表。
这里的问题是 SVG 路径语法允许逗号可选,但 CSS 不允许,而是生成 嵌套列表:对于像
"M 0%, 0% 50%,0%"
这样的路径,解析器将返回(概念上)这根本不是它应该提供的,即一个扁平列表或解释命令参数序列。
更糟糕的是,SVG 语法尽可能地允许空格可选。为了保持一致性和向下兼容性,您希望
"M 0 0 H 100"
等价于"M0,0H100"
(现在就是这样),但也等价于"M0px0pxH100px"
。但是 CSS 值规范 存在矛盾。我的结论是,为了避免重新定义完整的 CSS 解析器(可能需要进行重大更改),路径数据必须保留为字符串,并且其解释留给 SVG 解析器,并向其中添加单位和
calc()
函数。如何将var()
函数包含在其中,我一点也不清楚。因此,SVG 也有
clipPathUnits="objectBoundingBox"
。这要求所有无单位数字都是包含元素宽度(对于 X 值)和高度(对于 Y 值)的十进制倍数。这显然是响应式的。鉴于此,我很惊讶 CSS 路径人员没有将其设为路径的默认单位。它还能以某种方式作为选项添加吗?