CSS clip-path
属性 是 CSS 中最不被使用,但最有趣的属性之一。 它可以 与 CSS 形状结合使用 创建有趣的布局,并可以被推到极致以创建一些令人印象深刻的布局和动画,例如 Species in Pieces 项目。
在探索使用 CSS 和 SVG 创建任意形状的 UI 组件时,我突然意识到,clip-path
属性与 SVG 路径结合使用可以相当容易地创建圆形菜单,尤其是考虑到规范中处理被剪切区域的指针事件时的(预期)浏览器行为。 让我们进一步探讨这个想法。
一些背景
几年前,我在 Codrops 上发表了一篇文章,介绍了如何使用 CSS 变换和一些 CSS 技巧来创建仅 CSS 圆形菜单。 该技术很不理想也不直观,需要大量的变通方法和技巧才能达到预期效果。
要使用这种技术创建菜单,您需要通过倾斜菜单项来“伪造”扇形,然后通过隐藏其容器的溢出来剪切它们。 由于您是从倾斜菜单项开始的,因此您需要在之后“反倾斜”它们内部的内容,这些内容在容器倾斜后会变形。
生成的菜单不灵活,需要大量的技巧,而且其内部的内容在大多数情况下仅限于图标,因为其他内容很难在倾斜的项目内部定位和设置样式。 您可以在 文章中 了解该技术的全部细节。
如今,CSS clip-path
属性(与 SVG 路径相结合)也可以用于在 CSS 中创建圆形菜单。 该技术没有技巧,不需要任何奇怪的变换变通方法,而且按预期工作。 在撰写本文时,存在一些限制和浏览器错误(见下一节),但创建菜单所需的代码出奇地短小、干净且易于理解。
但在我们深入研究代码之前,尽管代码很简单易懂,但如果您还没有熟悉剪切路径是什么、它们的作用以及它们如何工作,您可能希望先了解有关剪切路径的更多信息。 您可以在 我的博客上的一篇文章 中了解所有关于它们的信息。
在深入研究代码和实际演示之前,我们还需要回顾一下 **有关浏览器支持的一些注意事项**
浏览器支持(和错误)
- 我们将使用 CSS
clip-path
属性,因此首先要注意的是浏览器支持。 如 CanIUse 中所示的 支持表,该属性的支持并不是最佳的,尤其是 IE 的任何版本都不支持它,甚至包括 MS Edge。 - IE 尚未支持 CSS 中的剪切路径,但目前 “正在考虑中”。
- 如果您使用 CSS 基本形状函数来定义剪切路径,Firefox 将不会应用该剪切路径,因为它 **目前仅支持引用 SVG
<clipPath>
元素的剪切路径值**。 Firefox 尚未支持基本的 CSS 形状函数。(有关解决方法,请参阅本节末尾。) - **基于 WebKit/Blink 的浏览器在使用 SVG 定义剪切路径时无法正确处理指针事件。** 这是一个错误。 默认情况下,指针事件不应该在被剪切元素的可见区域之外调度;这是规范规定的预期行为。 当您使用 CSS 基本形状函数来定义剪切路径时,这些浏览器会正确处理指针事件。 但是,当您通过
clip-path
属性应用 SVGclipPath
时,指针事件仍然在可见区域之外调度,这将干扰并阻止任何位于被剪切元素后面/下面的元素上的指针事件。 我在撰写本文时提交了一个 错误报告。 希望它能尽快得到修复。 此错误意味着 **本文中的演示目前在基于 WebKit/Blink 的浏览器中不起作用**。(抱歉。) - 在 Blink 基于浏览器的另一个错误,它会导致一个非常奇怪的渲染问题(这是一个合成问题),这也使得在悬停时对被剪切元素应用转换效果实际上变得不可能。 因此,对于我们文章中的菜单,例如,使用 CSS 缩放变换在单击时缩放菜单会造成很大的问题,因此我们将跳过打开/关闭效果。 我也为此问题提交了一个 错误报告。
简而言之:**本文的演示目前仅在发布时的 Firefox 中按预期工作。**
如果您决定更改文章中使用的剪切路径并将其替换为 CSS 基本形状,那么演示将在其他浏览器(不包括 IE/Edge)中工作,但在 Firefox 中不会工作。
**如果您确实想要使用 CSS 基本形状函数来定义剪切路径,并且希望它在 Firefox 中工作**,您始终可以使用 SVG <clipPath>
元素创建相同的形状,并将其作为 Firefox 的备用方案应用。
例如
.element {
clip-path: url(#SVGPolygonShape); /* For Firefox */
clip-path: polygon(...); /* For other browsers */
}
这里要注意的最后一件事是,目前只有 Firefox 支持引用在 *外部* SVG 中定义的 clipPath
元素;所有其他浏览器都需要将定义的 SVG 路径内联到文档中。 在 Chromium 项目中有一个 线程来跟踪此问题。
现在浏览器错误和支持已经明确,让我们深入研究代码。 我们将从菜单的标记开始。
标记它
标记非常简单:菜单是一个无序列表,包含包装在链接中的项目,并包含某种内容,例如文本或图标。 我正在使用一个简单的“图标”标签来进行演示。
<ul class="menu">
<li class="one">
<a href="#">
<span class="icon">icon-1</span>
</a>
</li>
<li class="two">
<a href="#">
<span class="icon">icon-2</span>
</a>
</li>
<li class="three">
<a href="#">
<span class="icon">icon-3</span>
</a>
</li>
<li class="four">
<a href="#">
<span class="icon">icon-4</span>
</a>
</li>
<li class="five">
<a href="#">
<span class="icon">icon-5</span>
</a>
</li>
<li class="six">
<a href="#">
<span class="icon">icon-6</span>
</a>
</li>
</ul>
这就是我们需要的全部标记。
定义剪切路径(扇形)
要创建扇形,我们需要能够在路径中间从一个点到另一个点绘制弧线,而这在 CSS 中使用 CSS 基本形状函数是无法实现的:circle()
、ellipse()
、inset()
或 polygon()
。(也就是说,除非您想使用 polygon()
函数,该函数具有非常多的点,这些点彼此非常靠近,以至于它们看起来像弧线。 但谁会愿意那样做呢?)
因此,我们需要一个更直接的方法来绘制扇形。 幸运的是,clip-path
属性允许我们通过接受对 SVG 路径的引用作为剪切路径的值来做到这一点。
换句话说,您可以在 SVG 中定义所需的剪切路径形状(它甚至可以由多个分离的路径组成),将该形状包装在带有 ID 的 SVG <clipPath>
元素中,然后使用 URL 语法在 CSS 中引用该路径
clip-path: url(#clipPathID);
因此,在 SVG 中定义我们的扇形变得非常简单。 但是,需要考虑一些数学和定位因素。
首先,您需要确定菜单中需要的项目数量;这将确定扇形中心角的值。
接下来,您需要使用 SVG <path>
元素绘制扇形。 SVG 中可用的路径命令使您能够按照一些简单的绘图规则绘制路径。
我们将使用四个路径命令来绘制扇形:M、l(小 L)、A 和 z。 但在我们开始绘制之前,我们需要确定扇形将在项目的哪里剪切,为此,我们需要在开始执行之前规划好概念。
确定剪切路径在菜单项上的位置
菜单项需要绝对定位在彼此之上,然后裁剪成扇形,这些扇形将共同构成菜单的整体圆形。
为此,我们需要将这些项目视为一系列图层(它们实际上就是如此),然后裁剪掉这些图层的部分,最终,只有这些图层的扇形部分可见。下图显示了我们正在创建的六个菜单项中的四个的图层。

半透明的黑色方框仅用于演示目的。实际发生的是*项目*正在旋转,而不是裁剪路径。也就是说,对于每个项目(矩形框),项目都被裁剪到完全相同的扇形(这样所有项目都相同),然后每个项目按必要的角度旋转,这样它们的裁剪区域就不会重叠,而是形成圆形菜单。下图更清楚地说明了这一点。

如果你看一下上图中的第二个扇形(试着稍微倾斜你的头,让黑色边框在上面)(是的,我在制作这些插图时倾斜了我的头),你将能够更清楚地看到扇形在项目中的位置:它与第一个项目中的扇形的位置完全相同,只是第一个项目在裁剪后没有旋转。
请注意,在上图的第二张图中,黑色方框确实代表了列表项在页面上的位置,因为裁剪路径会影响元素的可见/绘制区域,但实际上它仍然是一个矩形,即使只有一小部分通过它新的非矩形视口显示。
在我们继续之前,这里有一个动画版本,显示了如何定位、裁剪和旋转项目,以使用裁剪的元素实现整体菜单。这个 GIF 的目的是为了显示,在旋转之前,它们都是以相同的方式裁剪的。

即使元素被裁剪,它们在原理上仍然是矩形。所以当你可视化它们的旋转时,请记住,这是一个矩形元素正在旋转,但只有它内部的非矩形形状是可见的。
确定裁剪路径在应用于菜单项时的点坐标
现在我们知道如何旋转项目,我们知道初始扇形对于所有项目都将相同。所以接下来我们需要做的就是用 SVG 绘制扇形。为了做到这一点,我们需要看看定义扇形的点的坐标是如何确定的。
在我们解剖它之前,让我们看一下扇形代码;以下<svg>
进入页面,以便裁剪路径可以在 CSS 中被引用。
<svg height="0" width="0">
<defs>
<clipPath clipPathUnits="objectBoundingBox" id="sector">
<path fill="none" stroke="#111" stroke-width="1" class="sector" d="M0.5,0.5 l0.5,0 A0.5,0.5 0 0,0 0.75,.066987298 z"></path>
</clipPath>
</defs>
</svg>
上面代码中最重要的部分是clipPathUnits="objectBoundingBox"
声明。clipPathUnits
属性接受两个值:userSpaceOnUse
和objectBoundingBox
。前者将使用整个页面的坐标系绘制路径,而后者将使用项目的坐标系——这正是我们想要的。
当使用objectBoundingBox
时,用于绘制扇形路径的点的坐标使用范围为[0, 1]的相对值设置。**这些值在原理上与百分比值非常相似**,并将相对于元素的边界框计算;即元素的宽度和高度,在本例中。这就是为什么上面代码段中的值都小于 1。
如果你使用userSpaceOnUse
值而不是objectBoundingBox
,浏览器将使用页面上的坐标系(在 HTML 的情况下)或正在使用的当前用户坐标系(在 SVG 的情况下)来定位你的裁剪路径,这意味着它可能确实应用于你的元素,也可能没有应用于你的元素,具体取决于元素在页面或画布上的位置。你可以阅读更多关于这个主题的信息在我的文章中.
了解了扇形如何在菜单项(正方形)中定位,我们可以说明位置和相对点坐标。

这些坐标连同扇形的相对圆半径将由路径命令用来绘制扇形。
鉴于我们希望它在菜单项上的位置,定义裁剪路径的点的坐标可以如图像所示确定。绘制扇形需要三个点,我们还需要该扇形的圆半径。
圆的半径为 0.5,即元素宽度的二分之一。该圆的中心也在 (0.5, 0.5) 上。构成底部的扇形上的第二个点位于 (1, 0.5) 处。第三个点的坐标,通过一些简单的数学计算,位于 (0.75, 0.066987298) 处。然后,使用 SVG 圆弧 (**A**) 和线命令 (**l**) 绘制路径。我们不会详细介绍它是如何绘制的,因为它超出了本教程的范围,但这里有一个交互式演示,展示了这个绘制的实际操作。点击按钮播放。
查看笔 用 SVG 构建圆形菜单——#1 by Sara Soueidan (@SaraSoueidan) on CodePen.
要了解如何使用路径命令来绘制扇形,请参考我的博客上的这篇文章.
有了这些数据,我们就可以创建定义我们的<clipPath>
的<path>
,并准备将其应用于我们的菜单项。
裁剪菜单项
有了准备好的 SVG 裁剪路径,我们现在可以使用clip-path
属性裁剪菜单项。
首先,项目在菜单内绝对定位在彼此之上。我们从创建一个定位上下文开始。
.menu {
position: relative;
list-style: none;
margin: 30px auto;
/* padding trick for maintaining aspect ratio */
height: 0;
padding: 0;
padding-top: 70%;
width: 70%;
}
为了确保菜单具有正方形尺寸,我使用填充技巧来确保它保持 1:1 的纵横比。因为我们希望它具有流动性,我使用百分比值来表示宽度。使用媒体查询,你可以指定你想要的最小和最大尺寸,并相应地调整填充(用于技巧)。下面的实时演示包含这部分,所以你可以随意玩弄这些值并根据自己的喜好调整它们。
接下来,将项目定位在菜单内并裁剪到扇形。
.menu li {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
clip-path: url(#sector)
}
.menu li a {
display: block;
width: 100%;
height: 100%;
}
.menu li:hover {
background-color: gold;
}
这将裁剪所有项目到扇形,并且只会导致一个项目显示(最后一个,在所有其他项目的顶部),其余项目“隐藏”在它后面。其余项目不可见,因为它们都被裁剪在同一个区域,因此被最后一个项目覆盖。
为了显示其余的项目并形成我们的圆形菜单,我们需要按必要的角度旋转项目。项目 #1,索引为 0,将按0 * angle
= 0 * 60
= 0deg
旋转。
项目 #2 将按1 * 60
= 60deg
旋转;项目 #3 将按2 * 60
= 120deg
旋转。依此类推。
.one {
background-color: $base;
transform: rotate(0deg);
}
.two {
background-color: darken($base, 7%);
transform: rotate(-60deg);
}
.three {
background-color: darken($base, 14%);
transform: rotate(-120deg);
}
.four {
background-color: darken($base, 21%);
transform: rotate(-180deg);
}
.five {
background-color: darken($base, 28%);
transform: rotate(-240deg);
}
.six {
background-color: darken($base, 35%);
transform: rotate(-300deg);
}
我们可以使用 Sass 循环来自动执行此操作,但让我们不要偏离我们的重点太远。
这就是使用 CSS 裁剪路径获得圆形菜单项所需的一切。以下是实时演示。
查看笔 使用 CSS 裁剪路径创建圆形菜单 by Sara Soueidan (@SaraSoueidan) on CodePen.
请注意,由于 Chrome 在撰写本文时存在指针事件的错误,你可以将 SVG 裁剪路径引用替换为以下内容。
clip-path: polygon(50% 50%, 100% 50%, 75% 6.6%);
以了解菜单在具有适当指针事件时的工作方式。将鼠标悬停在项目上以查看它们的背景色变化。请记住,新的裁剪路径在 Firefox 中将无法正常工作。所以,暂时你可以添加上面的代码行在clip-path
URL 语法之后,这样 Firefox 就可以使用后者。
这就是使用polygon()
裁剪路径的菜单的外观。

我在上面的菜单中添加了这行 CSS,所以如果你想,可以取消注释。
在菜单项中添加图标/内容
…很简单。你所需要做的就是确保图标/文本/任何东西都可以在元素的可见区域内看到(扇形)。同样,使用元素高度和宽度建立的坐标系,你可以估计项目的位置,使它们出现在新的扇形视口中。

根据项目的具体内容,你可能需要进行一些实验才能获得你想要的精确对齐。
对于我们的演示,我使用的是文本,所以将文本定位在元素下方 30% 的位置,并向右边缘左侧 15% 的位置是一个不错的起点。
在为示例中的文本指定位置后,**我们需要将文本按与扇形相同的角度旋转**,以确保它在菜单中看起来像应该的那样,否则你最终会得到以下结果。

而不是这个。

项目内文本的样式如下。
.icon {
position: absolute;
/* exact values here depend on what you are placing inside the items (icon, image, text, etc.) */
right: 15%;
top: 30%;
/* angle of rotation = angle of the sector itself */
transform: rotate(60deg);
/* style further as needed */
}
另一种方法
以上技术是使用 CSS 裁剪路径实现圆形菜单的最简单方法。
你可以不用相同的扇形来裁剪所有元素,然后在 CSS 中旋转这些元素,而是可以使用**旋转的扇形**,这样每个菜单项都会被裁剪,使生成的扇形已经旋转成圆形。
但是,这种技术更加复杂,因为你需要为每个 SVG <path>
指定点坐标和旋转,更不用说它需要更多工作来获取每个项目的内容位置,以显示在这些扇形中。它更复杂、更耗时,因此肯定不如以上技术好。
也就是说,我仍然想向您展示在这种情况下剪裁路径的代码是什么样的。
<svg height="0" width="0">
<defs>
<clipPath clipPathUnits="userSpaceOnUse" transform="matrix(1,0,0,1,0,0)" id="one-2">
<path fill="none" stroke="#111" stroke-width="1" class="sector" d="M250,250 l250,0 A250,250 0 0,0 375,33.49364905389035 z"></path>
</clipPath>
<clipPath clipPathUnits="userSpaceOnUse" transform="matrix(0.5,-0.86602,0.86602,0.5,-91.5063509461097,341.5063509461096)" id="two-2">
<path fill="none" stroke="#111" stroke-width="1" class="sector" d="M250,250 l250,0 A250,250 0 0,0 375,33.49364905389035 z"></path>
</clipPath>
<clipPath clipPathUnits="userSpaceOnUse" transform="matrix(-0.49999,-0.86602,0.86602,-0.49999,158.49364905389024,591.5063509461097)" id="three-2">
<path fill="none" stroke="#111" stroke-width="1" class="sector" d="M250,250 l250,0 A250,250 0 0,0 375,33.49364905389035 z"></path>
</clipPath>
<clipPath clipPathUnits="userSpaceOnUse" transform="matrix(-1,0,0,-1,500,500)" id="four-2">
<path fill="none" stroke="#111" stroke-width="1" class="sector" d="M250,250 l250,0 A250,250 0 0,0 375,33.49364905389035 z"></path>
</clipPath>
<clipPath clipPathUnits="userSpaceOnUse" transform="matrix(-0.5,0.86602,-0.86602,-0.5,591.5063509461097,158.4936490538905)" id="five-2">
<path fill="none" stroke="#111" stroke-width="1" class="sector" d="M250,250 l250,0 A250,250 0 0,0 375,33.49364905389035 z"></path>
</clipPath>
<clipPath clipPathUnits="userSpaceOnUse" transform="matrix(0.5,0.86602,-0.86602,0.5,341.5063509461096,-91.5063509461097)" id="six-2">
<path fill="none" stroke="#111" stroke-width="1" class="sector" d="M250,250 l250,0 A250,250 0 0,0 375,33.49364905389035 z"></path>
</clipPath>
</defs>
</svg>
与前几节中的几行代码相比,这可是相当多的 SVG 代码。
上面的代码片段包含每个扇区的剪裁路径,根据需要旋转,并且没有将坐标转换为[0, 1]范围内的相对坐标;您也需要转换这些坐标。然后,可以将上面每个扇区用作菜单中相应元素的剪裁路径。这个方法不太理想,对吧?
请注意,上面的代码是从我之前创建的一个圆形菜单生成器生成的,所以,不,我没有手工编写这段代码。(下一节将详细介绍。)
SVG 圆形菜单
clip-path
技术非常棒,从概念上讲,它运作良好;但是当前的浏览器错误使得生成的菜单在今天实际上无法使用。
几个月前,我探索了使用 SVG 和 SVG 路径创建圆形菜单的想法,考虑到 SVG 通常非常适合创建非矩形形状。使用 SVG 路径,将绘制扇区形状并将其包装在链接<a>
中。创建扇区非常简单,需要与之前相同的考虑因素和步骤,除了您可以坚持使用点坐标的绝对值而不是相对值,因为整个 SVG 画布将是绘制扇区的坐标系。您不需要将扇区形状应用于任何元素——形状path
本身将成为菜单项。
因此,如果您确实需要在当今的 UI 中使用圆形菜单,那么 SVG 目前是您的最佳选择。支持率很高(IE9+ 和所有现代浏览器),并且按预期工作。此外,您将获得 SVG 的灵活性,尤其是在您可以使用 SVG 图标代替图标字体。如果您有兴趣,可以查看解释如何执行此操作的文章here。
由于在 SVG 中创建圆形菜单需要相当长的时间,我还创建了一个生成器,使您能够直观地自定义菜单的定义特征,然后为您生成该菜单的代码,准备嵌入。该工具还包括一个关于生成代码的详细指南,如何嵌入它,如何动画菜单打开/关闭,甚至还有一些关于何时使用圆形菜单的 UX 注意事项。

您可以在 here找到生成器。
最后的话
不要使用旧的 CSS 变换技术来创建圆形菜单。CSS 中的剪裁路径非常棒且功能强大,但直到支持改进之前,它们并不总是可行。SVG 也同样很棒,目前更适合创建圆形菜单,至少在 CSS 剪裁路径支持改进之前是这样。
我希望您喜欢这篇文章并发现它有用。感谢您的阅读!
太棒了!非常有用的属性。感谢您的文章。
在对外部 SVG 剪裁路径的
pointer-events
支持改进之前,您可以将单个项目的剪裁扩展到clip-path: polygon(50% 50%, 100% 50%, 100% 0, 77% 0%);
,以便它们的背景颜色延伸出来,并对主.menu
应用圆形clip-path
,例如clip-path: circle(35% at 50% 50%);
,以圆形地剪裁项目。我无法直接分叉您的 CodePen 演示,但我在这里重新创建了它:http://codepen.io/shshaw/pen/zGQwbq?editors=010
没错,这确实有效,但这是一种我想要避免的 hack。整个想法是不需要 hack 你的方法。只需创建形状并应用它。 “通过剪裁其父级来剪裁项目”是之前 CSS 技术中使用的一种解决方法,而整个想法是避免它。我仍然建议您暂时使用 SVG,直到支持改进,因为
clip-path
在 IE/Edge 中根本无法使用,很遗憾,所以这是一个概念验证。此外,好消息是我提交的错误已在进行中,因此我们可能会在下一个版本的 Chrome(耶!)中看到它得到修复。我希望如此。=)
感谢您提供的优秀文章:您相关的链接提供了宝贵的基础。
您能够在一个小时内(我猜您可能花了不止一个小时)向某人教授/传达的 信息量令人印象深刻!
再次感谢。
非常感谢 Max!很高兴您发现它有用!^^