使用 clip-path 剪切元素的内部区域

Avatar of Ana Tudor
Ana Tudor

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

以下是 Ana Tudor 的客座文章。您可能从她 令人惊叹的作品 中了解到 Ana,她将代码、数学和艺术结合在一起。在这里,她向我们展示了如何通过应用一些巧妙的几何图形来改变剪辑路径的正常行为,然后使其在不同的技术和浏览器中都能正常工作。

CSS 中的 clip-path 属性 用于隐藏路径**外部**的元素区域。但我们也可以通过这种方式剪切元素**内部**的区域。通过反转填充颜色,使用蒙版也可以实现相同的效果,但我在这里的目标是展示如何使用 clip-path 实现此效果。

路径需要有两个部分

  • 一个外部部分,确保其内部区域保持可见
  • 一个内部部分,用于移除外部路径内部区域的一部分

这两个部分需要通过一个**零宽度通道**连接起来。无论眼睛看到什么,一切都仍然在路径内部。

下面的演示展示了路径是如何绘制的,以及如何通过扩展通道的宽度来揭示看似边框的区域实际上是路径内部的区域。请注意,外部部分是逆时针绘制的,而内部部分是顺时针绘制的。

好的,让我们看看如何用 CSS 实现这个效果。我们唯一可以使用的基本形状是 polygon()。所以我们会得到类似这样的代码

clip-path: polygon(
  /* points of the outer triangle going anticlockwise */
  285px 150px, 83px 33px, 83px 267px, 
  
  /* return to the first point of the outer triangle */
  285px 150px, 
  
  /* points of the inner triangle going clockwise */
  258px 150px, 96px 244px, 96px 56px, 
  
  /* return to the first point of the inner triangle */
  258px 150px
);

您可以在这个笔中看到它的工作原理(WebKit/Blink 浏览器,Firefox 47-48 在 about:config 中将 layout.css.clip-path-shapes.enabled 设置为 true,以及 Firefox 49+,无需设置任何标志)

手动编写这么多代码很麻烦。而且,如果我们想要一个十二边形而不是三角形,代码会更多。这就是为什么我们可以使用 Sass 来自动生成规则多边形(所有边长相等且所有顶点相等的多边形)的点列表。

让我们看看它是如何工作的。

正如您在下面的演示中看到的,规则多边形的所有顶点都位于称为外接圆的圆上。

为了得到一个有 n 条边/顶点的规则多边形,我们需要在圆上均匀分布 n 个顶点,然后将第一个与第二个连接,第二个与第三个连接,依此类推,直到我们将最后一个连接回第一个。但是我们如何在圆上分布这些点呢?

圆上一个点的位可以由圆的半径以及该点与圆心连接的半径与沿 x 轴正方向的半径之间形成的角度来描述。

如上面的交互式演示所示,x 坐标是半径乘以它与水平线形成的角度的余弦。 y 坐标是半径乘以相同角度的正弦。我们将外半径取为任意值,该值最多为我们要剪切的元素的最小尺寸的一半,而内半径则略小。但是每个顶点的角度是多少呢?

如果我们将第一个顶点放在角度 0 处,我们将所有顶点均匀分布在圆上,并且圆周有 360 度,那么具有 n 个顶点的多边形的第 i 个顶点对应的角度将是 i*360deg/n

因此,我们的 Sass 代码首先为顶点数设置一个大于或等于 3 的值,计算基本角度(360deg/n),创建外半径和内半径变量、x 和 y 偏移变量——这些将指定外接圆的中心位置——以及内外三角形的空点列表。

$n: 3;
$base-angle: 360deg/n;
$r-outer: 150px;
$r-inner: 120px;
$offset-x: 50%;
$offset-y: 50%;
$points-inner: ();
$points-outer: ();

然后我们遍历顶点,计算每个顶点的坐标并将它们添加到相应的列表中

@for $i from 0 through $n {
  $points-outer: append(
    /* list of points for the outer polygon*/
    $points-outer, 

    /* x coordinate of current outer vertex */
    calc(#{$offset-x} + #{$r-outer*cos(-$i*$base-angle)}) 

    /* y coordinate of current outer vertex */
    calc(#{$offset-y} + #{$r-outer*sin(-$i*$base-angle)}), 
  
    comma) !global;

  $points-inner: append(
    /* list of points for the inner polygon*/
    $points-inner, 

    /* x coordinate of current inner vertex */
    calc(#{$offset-x} + #{$r-inner*cos($i*$base-angle)}) 

    /* y coordinate of current inner vertex */
    calc(#{$offset-y} + #{$r-inner*sin($i*$base-angle)}), 
  
    comma) !global;
}

这里有一些需要注意的重要事项。首先,在循环中,我们有 $i from 0 through $n,这意味着我们将 $n + 1 组坐标添加到我们的列表中。这不是错误,因为对于外部和内部多边形,我们都需要返回到第一个顶点以通过零宽度通道连接它们。其次,每个顶点的当前角度在外部多边形的情况下为 -$i*$base-angle,在内部多边形的情况下为 $i*$base-angle。这是因为外部多边形是逆时针方向,而内部多边形是顺时针方向。

此时,我们有两个列表,分别包含外部多边形的顶点和内部多边形的顶点。现在我们需要做的就是在将它们放入多边形函数时将它们连接起来。

clip-path: polygon(join($points-outer, $points-inner));

您可以在这个笔中实时体验它。更改 $n 的值以查看多边形是如何变化的(WebKit/Blink 浏览器,Firefox 47-48 在 about:config 中将 layout.css.clip-path-shapes.enabled 设置为 true,以及 Firefox 49+,无需设置任何标志)

我们过去是如何做到的

以下部分为了网页历史记录的原因而保留,但现在已过时。在撰写本文时,Firefox 仅支持使用 SVG 引用值进行 clip-path,并且支持 Internet Explorer/Edge(Chromium 之前的版本)——从未在 HTML 元素上支持 clip-path——是一个常见的要求。如果您对 2015 年初我们必须如何做事情不感兴趣,您可以 跳到最后三个演示

对于 Firefox,我们需要使用对 SVG 元素的引用。我们将把它放在 HTML 中

<svg width="0" height="0">
  <defs>
    <clipPath id="cp">
      <path d="M285,150 L83,33 L83,267 
               L285,150
               L258,150 L96,244 L96,56
               L258,150z"></path>
    </clipPath>
  </defs>
</svg>

在这里,路径数据的第 1 行包含外部多边形顶点的坐标,第 2 行包含其第一个顶点的坐标,第 3 行包含内部多边形顶点的坐标,第 4 行重复第二个多边形的第一个顶点的坐标。

在 CSS 中,我们只需使用

clip-path: url(#cp);

现在这在 Firefox 中可以工作,您可以在这个演示中看到

但是,这里有一些需要注意的事项。

我们不能像在 polygon() 函数内部那样混合使用单位。路径数据中的数字是硬编码的,并且根本不灵活。此外,如 之前所述,Chrome/Opera 将我们 clip-path0,0 点视为屏幕的左上角,而不是我们要剪切的元素的左上角。我们可以通过将 clipPathUnits 属性的值从 userSpaceOnUse(默认值)更改为 objectBoundingBox 来解决此问题。这样做迫使我们更改路径中的值,这些值现在都应缩放至 [0, 1] 区间。在 Chrome 中更改剪切元素的变换仍然存在错误

至于使其变得灵活,我们只需使用 JavaScript 重现我们使用 Sass 所做的事情。

var n = 3, 
    base_angle = 2*Math.PI/n, 
    r_outer = .5, 
    r_inner = .4, 
    offset_x = .5, 
    offset_y = .5, 
    points_outer = '', 
    points_inner = '', 
    angle, x, y;

for(var i = 0; i <= n; i++) {
  angle = i*base_angle;
  x = Math.cos(angle);
  y = Math.sin(angle);
  
  points_outer += ((i === 0)?'M':' L') + 
    (offset_x + r_outer*x).toFixed(3) + ', ' + 
    (offset_y - r_outer*y).toFixed(3);
  points_inner += ' L' + 
    (offset_x + r_inner*x).toFixed(3) + ', ' + 
    (offset_y + r_inner*y).toFixed(3);
}

document.querySelector('#cp path').setAttribute('d', points_outer + points_inner + 'z');

如果您想知道为什么我们有 - r_outer*y 而其他三个类似的项是 +,那是因为外部和内部多边形必须沿相反方向绘制(此处选择外部多边形为逆时针方向,内部多边形为顺时针方向)。这意味着我们应该在每一步都使用 -angle 来计算外部多边形顶点的位,并使用 angle 来计算内部多边形的顶点。但是 cos(angle) = cos(-angle)(相同符号),而 sin(angle) = -sin(-angle)(相反符号)。由于我们将 cos(angle) 存储在 x 中,将 sin(angle) 存储在 y 中,因此我们获得了当前点相对于偏移量的坐标:外部多边形为 r_outer*x, r_outer*-y,内部多边形为 r_inner*x, r_inner*y

您可以在这个笔中体验所有这些(更改 n 变量的值会更改整个多边形)

如果您想在 IE 中也获得相同的视觉效果,则剪切的元素应为 SVG 元素。初始标记变为

<svg width="300" height="300">
  <defs>
    <clipPath id="cp" clipPathUnits="objectBoundingBox">
      <path d=""></path>
    </clipPath>
  </defs>
  
  <rect class="clip-me" width="300" height="300"></rect>
</svg>

您可以在这个笔中体验它

演示

您可以将零宽度通道方法用于更多用途。例如,剪切元素中间的区域。

或者您可以使用它来剪切除几个不连接的区域之外的所有内容。当使用对 元素的引用时,这很容易实现,但是区域之间的零宽度通道是使用 CSS 中的 polygon() 值获得此结果的唯一方法。

当然,还可以选择多个多边形,并将内部剪切掉。