探索CSS Masking模块:Clipping

发布于 大漠

最近业务中需要实现一些镂空的效果。看到这些效果我最初想到的是通过CSS的maskclip-path来实现,甚至还想结合SVG相关的特性。为了验证自己的一些想法,开始着手撸码,但问题来了,在撸码的过程中,我发现自己对于mask的相关特性理解的还不够透彻。因此重新阅读CSS Masking Module Level 1规范中的文档和相关教程。才发现原来CSS Masking是如此的强大。下面是我自己对CSS Masking模块的一些理解,希望对大家学习和了解该功能模块有所帮助。

简介

我对CSS Masking模块的最初认知:

CSS Masking就是CSS的mask属性(或mask相关的属性)!

事实上,CSS Masking模块中包含了两个部分:masking(即:mask)和 clippingclip-path)。

这两种方式都可以帮助我们实现一些类似下图这样的镂空效果:

我将 Masking译为遮罩。那怎么理解遮罩呢?简单或形象的理解,大至是这样的一个意思:

  • 我有一个源,比如我们的页面
  • 我有一个层(遮罩层),它可能是一张图像或一个图形元素
  • 将两者合在一起,源在遮罩层下面

也就是说,Masking描述了如何使用另一个图形元素或图像作为亮度(luminance)或alpha遮罩。遮罩层可以是一张图像也可以是SVG绘制的图形,通常使用CSS或SVG渲染出来的元素(包括其子元素)会被绘制到缓冲区,然后该缓冲区将被组合到元素的父元素中。官方这段描述我个人认为过于拗口,自己简单的理解正如上图,图像源和遮罩层将会创建一个缓冲区,并且源和遮罩层会有一个合成的计算,在合层阶段之前,亮度和Alpha遮罩会影响这个缓冲区的透明度。如此得到一个类似镂空的效果。

Cliping(剪切)描述了视觉元素的可见区域。可以使用某些SVG图形或基本形状来描述该区域。此区域之外的任何内容都将看不到。

简单地说,该规范定义了两种不同的图形操作,即,完全部分地隐藏了对象的部分。

两者区别之处:

  • 剪切需要一个剪切路径,剪切路径可以是一个闭合矢量路径形状多边形;剪切路径是一个区域,该区域内部的所有内容都可以显示出来,外部的所有内容将被剪切掉,在页面上不可见
  • 遮罩需要一个高亮或Alpha遮罩层,将源和遮罩层合在一起会创建一个缓冲区域,在合层阶段之前,亮度和Alpha遮罩会影响这个缓冲区的透明度,从而实现完全或部分遮罩源的部分

注意:虽然遮罩提供了许多增强图形效果的可能性,并且通常对内容的可见部分提供了更多的控制,但是剪切路径可以执行得更好,基本形状可以更容易插值。

相关术语

在深入学习和了解MaskingClipping相关的知识前,我们先了解一些有关于这方面的术语。

源对象

不管是Masking还是Clipping中都会有一个源(对象)的概念。这个源可能是一个HTML元素,比如一个<img />元素。

路径或基本图形

在Clipping中对源对象进行剪切时,需要一个图形,而这个图形可以是一些基本图形,也可以是一个闭合的矢量路径。在CSS中,我们可以通过clip-path来绘制这些图形:

正如上面示例所示,可以绘制:

  • 正方形:inset()
  • 圆形:circle()
  • 椭圆形: ellipse()
  • 多边形:polygon()
  • url()

其中inset()circle()ellipse()绘制基本图形,polygon()绘制多边形,另外url(#id)配合SVG的clipPath来绘制更多圆滑的路径(这个也被称闭合的矢量路径)。怎么通过这些函数来绘制图形,后面介绍clip-path的时候再述。

遮罩层

在Masking中有一个遮罩层,这个遮罩层是一个图像(渐变绘制的也是),该层也被称为遮罩模式,主要有高亮Alpha两种模式。

Alpha模式:带有alpha通道的图像,alpha通道是包含在每个像素数据中的透明度信息。最简单的例子是带有黑色和透明区域的PNG图像。遮罩图像黑色部分将会显示(alpha的值是1),透明区域(alpha的值为0)内容将会隐藏:

高亮模式使用图像的亮度值作为遮罩值。如下图所示,遮罩层白色区域将显示出来,透明区域将会被隐藏:

最终得到的效果看上去相似:

Clipping

从上面的示例中,我们可以发现,剪切可以限制渲染区域,即所谓的剪切区域。从概念上讲:

剪切路径(不管是图形还是闭合的矢量路径)对某个元素进行剪切,将会分成两个区域,路径内(剪切区域内) 和 路径外(剪切区域外),位于剪切区域内的部分可见(被绘制出来)

在Clipping中会用到clip-pathclip-rule<clipPath>

  • clip-path:用来绘制图形
  • clip-rule:用于确定给定点是否位于图形元素创建的剪贴区域的形状内的算法
  • <clipPath>:是SVG中的一个标签元素,可以被用于clip-pathurl()中,当作剪切路径源

接下来了解这三个属性。

clip-path

clip-path主要用来创建一个只有元素的部分区域可以显示的剪切区域,区域内可见,区域外不可见。其值主要有:

  • none:未创建任何剪切区域
  • <basic-shape>:CSS中绘制基本形状的函数,常见的函数有inset()circle()ellipse()polygon()url()path()
  • <geometry-box>:如同<basic-shape>一起声明,它将为<basic-shape>提供相应的参考框盒子。通过自定义,它将利用确定的盒子边缘包括任何形状边角(比如说,被border-radius定义的剪切路径)

<geometry-box>提供的框盒模式主要有:

  • margin-box:使用margin box作为引用框。其主要由margin的外部边缘包围的形状。这种形状的圆角半径由相应的border-radiusmargin的值来决定。如果border-radiusmargin的比是1或更大,则边距框(margin-box)的半径是border-radius + margin。反之,如果两者的比小于1,那么边距框的半径是border-radius + (margin * (1 + (ratio-1)^3))ratioborder-radiusmargin的比,即border-radius / margin
  • border-box:使用border box作为引用框。定义了border外部边缘包围的形状,此形状遵循border外部边缘所有常规的border-radius规则
  • padding-box:使用padding box作为引用框。定义了padding外部边缘包围的形状,此形状遵循padding外部边缘所有常规的border-radius规则
  • content-box:使用content box作为引用框。定义了content外部边缘包围的形状,此框的每个border-radius都大于0border-radius - border-width - padding
  • fill-box:利用对象边界框作(Object bounding box)为引用框
  • stroke-box:使用笔触边界框(Stroke bounding box)作为引用框
  • view-box:使用最近的SVG视窗(Viewport)作为引用框。如果viewBox属性被指定来为元素创建SVG视窗,引用框将会被定位在坐标系统的原点,引用框位于由viewBox属性建立的坐标系的原点,引用框的尺寸用来设置viewBox属性的宽和高

如果是一个HTML元素被剪切,可以使用margin-boxborder-boxpadding-boxcontent-box框盒模式;如果运用于一个SVG元素上,可以使用fill-boxstroke-boxview-box

先来看绘制基本函数的使用。

inset()

inset()用来绘制矩形,或者带圆角的矩形。

clip-path: inset( <length-percentage>{1,4} [ round <'border-radius'> ]? )
  • <length-percentage>用来设置剪切区域距离上、右、下和左侧外边缘的距离;可以设置一个、两个、三个或四个值。如果设置一个值,表示上右下左四个值相等;如果设置两个值,表示上下取第一个值,左右取第二个值;如果取值三个值,表示上取第一个值,左右取第二值,下取值第三个值;如果取值四个值,表示上取第一个值,右取第二个值,下取第三个值,左取第四个值。类似于border的取值
  • round用来设置裁剪区域是否带圆角,未显式设置表示剪切区域没有圆角,如果设置表示剪切区域带有圆角
  • <border-radius>用来设置border-radius的半径值,其使用和CSS的border-radius一样,也可以带/

用张图来表示:

来看个Demo:

circle()

circle()用来绘制圆。

clip-path: circle(<shape-radius> at posX posY)
  • <shape-radius>:圆的半径,其值可以是 <length-percentage>closest-sidefarthest-side
  • at:显式的设置圆心的位置,如果未显式设置,默认的圆心在元素的正中间,即center50% 50%
  • posX posY:设置圆心位置,posXx轴的位置,posYy轴的位置,取值可以类似于background-position

ellipse()

ellipse()用来绘制椭圆:

clip-path:  ellipse( [ <shape-radius>{2} ]? [ at <position> ]? )
  • <shape-radius>:椭圆的半径,其值可以是 <length-percentage>closest-sidefarthest-side,如果只取一个值的时候,表示x轴和y的半径值是一样的
  • at:显式的设置圆心的位置,如果未显式设置,默认的圆心在元素的正中间,即center50% 50%)
  • <position>:即posX posY,用于设置圆心位置,如果未显式设置,其圆心在元素正中间(center)50% 50%,其使用类似于background-position

polygon()

polygon()主要用来绘制不规则的多边形:

clip-path: polygon( <fill-rule>? , [ <length-percentage> <length-percentage> ]# )
  • <fill-rule>:填充规则,后面介绍clip-rule规则会细节,具体解释可以阅读SVG中的fill-rule相关的规范
  • <length-percentage> <length-percentage>设置多边形点的位置

一个多边形至少会有三个点组成,比如简单的三角形,如果使用polygon()来绘制一个三角形的话,那么就会有三对坐标点,每对之间用一个,分隔,比如:

看一个示例:

使用polygon()函数绘制多边形形状,我们可以借助Clippy在线工具来帮助我们随意发挥:

而且在Firefox浏览器中,还可以使用Shape Path Editor调试器来帮助我们在浏览器中调试polygon()。有关于这方面更详细的介绍,可以阅读 @Mikael Ainalem 的《8 Little Videos About the Firefox Shape Path Editor》一文。

另外,clip-path: polygon()的组合,可以绘制一些更有趣的效果,比如分块(In Pieces)的效果:

上图的效果录制于@lukehaas写的示例:Animated Pokémon with CSS Clip-Path

@Bryan James 早在2015年在SmashingMagazine网站上就写过一篇博文,详细介绍了使用clip-pathpolygon()来实现分块的效果。国内 @拴萝卜的棍子 大大也对该技术做过深入的分析《clip-path打造的3D模型渲染器》。

path()

polygon()函数可以帮助我们绘制多边形形状,虽然Clippy在线工具可以帮助我们绘制很多常见的形状,但还是有所限制。不过庆幸的是,我们可以使用一些矢量图编辑软件,比如优秀的Sketch软件,可以在该软件中使用路径工具帮助我们绘制一些更有意思的形状。

比如绘制这样的一条路径,可以获取到其路径的值:

<polygon points="15,99 30,87 65,99 85,55 122,57 184,73 198,105 199,150 145,159 155,139 126,120 112,138 80,128 39,126 24,104"/>

可以在polygon()中对应的使用:

clip-path: polygon(15px 99px, 30px 87px, 65px 99px, 85px 55px, 122px 57px, 184px 73px, 198px 105px, 199px 150px, 145px 159px, 155px 139px, 126px 120px, 112px 138px, 80px 128px, 39px 126px, 24px 104px);

事情实不需要这么麻烦,在clip-path中还提供了一个path()函数。可以让我们直接在clip-path中使用绘制出来的路径。假设我们绘制了一个心形的路径:

上图来自于@Chris Coyier的《An Initial Implementation of clip-path: path();》一文。

不过该Demo目前仅在Firefox Nightly浏览器,而且要开启layout.css.clip-path实验功能才能看到效果。

url()

path()可以让我直接使用SVG的路径,但碍于浏览器的支持度有限,还无法使用。不过这并不太要紧,clip-path还提供了一个url()函数,可以让我引用SVG中<clipPath>

首先使用Sketch软件,绘制了一个心形路径:

导出来的SVG代码如下:

<svg width="500px" height="500px" viewBox="0 0 432 417" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <path d="M237.050781,129.945312 C323.824219,-20.9869792 385.207031,-38.9270833 421.199219,76.125 C457.191406,191.177083 395.808594,304.303385 237.050781,415.503906 C46.2721354,283.78659 -28.9544271,170.660288 11.3710937,76.125 C51.6966146,-18.4102878 126.923177,-0.470183653 237.050781,129.945312 Z" stroke="#979797"></path>
    </g>
</svg>

这个时候可以清理一下上面的代码,将<path></path>数据放到<clipPath>中,并且给<clipPath>元素添加一个id,比如svgPath

<svg height="0" width="0">
    <defs>
        <clipPath id="svgPath">
            <path d="M237.050781,129.945312 C323.824219,-20.9869792 385.207031,-38.9270833 421.199219,76.125 C457.191406,191.177083 395.808594,304.303385 237.050781,415.503906 C46.2721354,283.78659 -28.9544271,170.660288 11.3710937,76.125 C51.6966146,-18.4102878 126.923177,-0.470183653 237.050781,129.945312 Z" stroke="#979797"></path>
        </clipPath>
    </defs>
</svg>

接着要需要裁剪的对象上使用clip-path: url(#svgPath)即可:

采用该技术,我们可以实现很多有意思的效果:

这里运用到了<clipPath>,而<clipPath>是SVG中相关的知识,接下来会花一定的篇幅来介绍该元素,以及怎么更好的结合在clip-path中,实现一些较好的效果!

<clipPath>

前面提到过,CSS中的基本图形函数允许我们创建一定数量的图形,其中polygon()函数可以创建更为复杂的多边形。但是它也有一定的局限性:

如果你想创建一个更为复杂的图形,而且图形看起来不是用直线绘制出来的

而且上文中演示的必形路径(SVG中的path)被运用于clip-path:url()中,不是硬生生的直线,而是使用SVG的<clipPath>元素。正如<clipPath>元素名称所暗示的一样,你可以使用这个元素绘制任意路径的图形。即:

使用<clipPath>元素绘制出任意图形来做为一个剪切路径

SVG中的<clipPath>元素可以定义一个剪切路径,而且定义好的剪切路径可以用于clip-path中,一般可用于clip-pathurl()函数中。

<clipPath>元素可以包含很多元素,比如<path><text><circle><rect><polygon><use>元素。好果<use>元素是<clipPath>的子元素,那么它必须直接引用<path><text>基本形状元素。如果间接引用会报相应的错误

每个子元素的原始几何形状但不包括相关的渲染属性(比如fillstrokestroke-width等)将会在clipPath中概念上定义一个1字节遮罩(1-bit mask)(可能除了几何边缘的抗混叠之外),简单地说就是表示 与该元素关联的图形的轮廓。物体轮廓以外的任何东西都将被遮盖(无法可见)。如果子元素被displayvisibility设置为不可见,则不会影响剪切路径。当<clipPath>元素包含多个子元素时,子元素的轮廓在逻辑上或在一起创建一个剪影,然后用于限制可以应用到渲染区域。因此,如果一个点位于<clipPath>的任何子节点内,则该点位于剪切路径内

SVG中可用于<clipPath>元素的子元素有很多个,而且用于该元素的属性也不少,但其中最为有用的应该是clipPathUnits属性。

clipPathUnits属性

clipPathUnits属性主要用来给<clipPath>元素内容指定个坐标系统。它具有两个值:objectBoundingBoxuserSpaceOnUse,其中userSpaceOnUse是其默认值。

userSpaceOnUse

<clipPath>元素是用来当作参考物时,<clipPath>元素内容是以用户坐标系统作为参考点。例如,clipPath元素的用户坐标系统是通过clip-path属性来引用。

用户坐标系统(局部坐标系统)是目前激活的坐标系统,主要用来如何定位坐标和长度。一个HTML元素的坐标和CSS的盒模型有关,但不同的是SVG元素没有这样的盒模型。

对于CSS盒子的布局,用户的坐标原点就是盒子的 左上角,而且一个单位就是一个像素,视窗也可以根据盒子的宽度按百分比计算。如果你有一个<clipPath>元素,它里面包含了一个<circle>子元素,而且这个<circle>元素的中心点在cx=100cy=100。那么这个中心点就是距盒子左侧100px和顶边100px的交汇处。

如果元素是一个SVG元素,因此他是没一个类似于CSS盒模型的东西,用户的坐标原点是距<svg>元素视窗左上角最近的一个地方。一般情况之下,最近的视窗的建立,他的宽度和高度接近于<svg>的祖先元素。如果你不嵌套<svg>元素,它就是你创建的<svg>元素。

注意,SVG元素的坐标系统可以使用viewBox属性进行修改,其他属性可能有助于改变坐标系统。这一部分的内容超出了本文的内容范围。所以在本文中,我假设viewBox没有进行过任何的修改。因此浏览器使用的默认坐标系统原点是在<svg>元素的左上角,大小也等于<svg>元素。

objectBoundingBox

坐标系统的原点是在元素的边框盒子的左上角顶点处,同样适于剪切路径。这个边框是SVG元素对象的边框(它只是包含了一个或多个几何图形形状)和一个HTML元素设置border-box的盒模型是相关联的。

这个值对SVG元素非常有用,因为它允许你应用的元素自身的边界做为剪切路径。下图显示一个图像应用SVG的剪切路径显示的效果,他们分别使用了userSpaceOnUseobjectBoundingBox。灰色的边框表示的是SVG元素创建的一个视窗。右图中的图像,我添加了一个灰色的边框用来表示剪切后的图像边框。

特别声明,上图来自于 @SaraSoueidan 的《The clip-path Property and <clipPath> Element》一文。

在左图中,剪切路径的坐系统定位在SVG的视窗上。当使用了objectBoundingBox属性之后,图像自身的边框就会做为剪切路径的坐标系统。

有一点需要特别的注意:当你设置了objectBoundingBox值后,<clipPath>元素中的内容必须在指定的坐标[0,1]内。坐标系统将成为一个单元系统,剪切出来的形状都在这个clipPath分值内。

例如,如果剪切路径包含一个<circle>元素,而且他定位在圆的中心上:

<clipPath>
    <circle cx="350" cy="350" r="300" />
</clipPath>

圆的位置(半径)会用分数表示:

<clipPath clipPathUnits="objectBoundingBox">
    <circle cx=".5" cy=".5" r=".45" />
</clipPath>

在这种情况下,分数就像百分比。例如下面这个示例:

clipPath使用细节

CSS属性会将其祖先元素中继承琶<clipPath>元素,但属性不会从引用的<clipPath>元素的元素继承。

从上面的小示例中不难发现,<clipPath>元素不会直接在页面中渲染出来,它唯一的用途是 作为clip-path属性可引用的东西,即 常用于clip-path属性的url()函数中。因此,像display这样的属性不应该运用于<clipPath>元素上。因此,即使display属性设置了非none的值,<clipPath>也不会直接在页面中呈现出来。另外,就算是在<clipPath>元素或其任何祖先元素上设置display:none,在clip-path中还是可以引用<clipPath>提供的路径。

另外剪切路径在概念上等价于引用元素的自定义视图。因此,它影响元素的渲染(呈现),但不影响元素固有的几何形状。剪切元素(即,通过clip-path属性引用<clipPath>元素的元素,或引用元素的子元素)的包围框必须保持与未剪切元素相同。

默认情况下,指针事件(Pointer Events)不能在形状的剪切(不可见)区域上分派(不生效)。例如,一个半径为10的圆被裁剪切到一个半径为5的圆上,将不会接收到较小半径之外的单击事件。

即:

clip-rule

clip-rule属性表示用于确定给定点是否位于图形元素创建的剪切区域内形状的算法。算法和clip-rule属性的值定义遵循SVG中fill-rule属性的定义

clip-rule属性有两个值:nonzeroevenodd

先来看SVG中对fill-rule属性的相关描述。

nonzero

这个值采用的算法是:从需要判定的点向任意方向发射线,然后计算图形与线段交点的处的走向;计算结果从0开始,每有一个交点处的线段是从左到右的,就加1;每有一个交点处的线段是从右到左的,就减1;这样计算完所有交点后,如果这个计算的结果不等于0,则该点在图形内,需要填充;如果该值等于0,则在图形外,不需要填充。比如下图:

来看一个示例:

<svg width="250px" height="250px" viewBox="0 0 250 250"> 
    <polygon fill="#F9F38C" fill-rule="nonzero" stroke="#E5D50C" stroke-width="5" stroke-linejoin="round" points="47.773,241.534 123.868,8.466 200.427,241.534 7.784,98.208 242.216,98.208 " /> 
</svg> 

<svg width="250px" height="250px" viewBox="0 0 250 250"> 
    <path fill="#F4CF84" fill-rule="nonzero" stroke="#D07735" stroke-width="5" d="M124.999,202.856 c-42.93,0-77.855-34.928-77.855-77.858s34.925-77.855,77.855-77.855s77.858,34.925,77.858,77.855S167.929,202.856,124.999,202.856z M125.003,245.385c-7.61,0-13.025-6.921-17.802-13.03c-2.79-3.559-6.259-8.002-8.654-8.638c-0.318-0.085-0.71-0.134-1.159-0.134 c-2.873,0-7.1,1.698-11.188,3.335c-4.929,1.973-10.029,4.021-14.774,4.021c-2.486,0-4.718-0.563-6.633-1.677 c-6.451-3.733-7.618-11.959-8.742-19.919c-0.646-4.571-1.45-10.261-3.292-12.096c-1.84-1.845-7.524-2.646-12.093-3.298 c-7.96-1.119-16.192-2.286-19.927-8.739c-3.682-6.358-0.614-14.005,2.35-21.404c1.829-4.563,3.904-9.735,3.201-12.352 c-0.638-2.392-5.073-5.861-8.64-8.648C11.539,138.025,4.618,132.612,4.618,125c0-7.61,6.921-13.025,13.027-17.802 c3.567-2.79,8.002-6.259,8.64-8.651c0.702-2.614-1.375-7.789-3.201-12.349c-2.961-7.399-6.029-15.046-2.347-21.409 c3.733-6.451,11.962-7.618,19.924-8.742c4.569-0.646,10.253-1.45,12.096-3.292c1.84-1.84,2.646-7.524,3.29-12.093 c1.127-7.96,2.291-16.192,8.745-19.924c1.914-1.111,4.147-1.674,6.633-1.674c4.745,0,9.845,2.045,14.771,4.021 c4.085,1.639,8.312,3.335,11.188,3.335c0.446,0,0.836-0.045,1.161-0.131c2.392-0.641,5.861-5.079,8.654-8.643 c4.782-6.109,10.194-13.03,17.804-13.03c7.612,0,13.025,6.921,17.804,13.027c2.782,3.565,6.259,8.002,8.654,8.643 c0.323,0.085,0.71,0.131,1.161,0.131c2.876,0,7.094-1.696,11.185-3.332c4.932-1.976,10.029-4.021,14.779-4.021 c2.478,0,4.715,0.563,6.627,1.671c6.448,3.733,7.618,11.962,8.739,19.927c0.646,4.569,1.453,10.253,3.292,12.093 c1.84,1.84,7.524,2.646,12.096,3.292c7.96,1.127,16.189,2.291,19.919,8.745c3.687,6.36,0.619,14.007-2.344,21.404 c-1.824,4.563-3.898,9.735-3.201,12.347c0.641,2.395,5.079,5.864,8.643,8.657c6.104,4.774,13.025,10.189,13.025,17.799 c0,7.612-6.921,13.025-13.03,17.804c-3.559,2.788-8.002,6.264-8.638,8.654c-0.702,2.614,1.375,7.783,3.201,12.347 c2.964,7.399,6.032,15.046,2.344,21.409c-3.733,6.448-11.959,7.618-19.924,8.739c-4.566,0.646-10.256,1.453-12.09,3.292 c-1.845,1.84-2.646,7.524-3.298,12.096c-1.119,7.96-2.291,16.189-8.745,19.919c-1.909,1.113-4.147,1.677-6.627,1.677 c-4.745,0-9.839-2.048-14.768-4.021c-4.091-1.637-8.315-3.335-11.19-3.335c-0.446,0-0.836,0.048-1.161,0.134 c-2.392,0.635-5.861,5.073-8.648,8.638C138.027,238.464,132.615,245.385,125.003,245.385z" /> 
</svg>

效果如下:

星星是由一条相交的路径组成的,太阳则是由一条长复合的路径组成。每个形状的内部最初并不清楚,可能根据作者的意图而有所不同。在这些情况下,fill-rule允许进一步澄清。

在下一个例子中,我们可以看得更清楚些,当nonzero算法被应用到类似的图形时,究竟发生了什么?

从上图中我们可以理解成,当方向是顺时针时,加1,逆时针时减1。相交的值不等于0则填充,如果等于0则不填充。

evenodd

这个值采用的算法是,从需要判定的点向任意方向发射线,然后计算图形与线段交点的个数,个数为奇数则该点在图形内,则需要填充;个数为偶数,则该点在图形外,不需要填充。如下图所示:

上面的示例稍作调整:

<svg width="250px" height="250px" viewBox="0 0 250 250">
    <polygon fill="#F9F38C" fill-rule="evenodd" stroke="#E5D50C" stroke-width="5" stroke-linejoin="round" points="47.773,241.534 123.868,8.466 200.427,241.534 7.784,98.208 242.216,98.208 " />
</svg>

<svg width="250px" height="250px" viewBox="0 0 250 250">
    <path fill="#F4CF84" fill-rule="evenodd" stroke="#D07735" stroke-width="5" d="M124.999,202.856
    c-42.93,0-77.855-34.928-77.855-77.858s34.925-77.855,77.855-77.855s77.858,34.925,77.858,77.855S167.929,202.856,124.999,202.856z
    M125.003,245.385c-7.61,0-13.025-6.921-17.802-13.03c-2.79-3.559-6.259-8.002-8.654-8.638c-0.318-0.085-0.71-0.134-1.159-0.134 c-2.873,0-7.1,1.698-11.188,3.335c-4.929,1.973-10.029,4.021-14.774,4.021c-2.486,0-4.718-0.563-6.633-1.677 c-6.451-3.733-7.618-11.959-8.742-19.919c-0.646-4.571-1.45-10.261-3.292-12.096c-1.84-1.845-7.524-2.646-12.093-3.298 c-7.96-1.119-16.192-2.286-19.927-8.739c-3.682-6.358-0.614-14.005,2.35-21.404c1.829-4.563,3.904-9.735,3.201-12.352 c-0.638-2.392-5.073-5.861-8.64-8.648C11.539,138.025,4.618,132.612,4.618,125c0-7.61,6.921-13.025,13.027-17.802
    c3.567-2.79,8.002-6.259,8.64-8.651c0.702-2.614-1.375-7.789-3.201-12.349c-2.961-7.399-6.029-15.046-2.347-21.409 c3.733-6.451,11.962-7.618,19.924-8.742c4.569-0.646,10.253-1.45,12.096-3.292c1.84-1.84,2.646-7.524,3.29-12.093 c1.127-7.96,2.291-16.192,8.745-19.924c1.914-1.111,4.147-1.674,6.633-1.674c4.745,0,9.845,2.045,14.771,4.021 c4.085,1.639,8.312,3.335,11.188,3.335c0.446,0,0.836-0.045,1.161-0.131c2.392-0.641,5.861-5.079,8.654-8.643
    c4.782-6.109,10.194-13.03,17.804-13.03c7.612,0,13.025,6.921,17.804,13.027c2.782,3.565,6.259,8.002,8.654,8.643 c0.323,0.085,0.71,0.131,1.161,0.131c2.876,0,7.094-1.696,11.185-3.332c4.932-1.976,10.029-4.021,14.779-4.021 c2.478,0,4.715,0.563,6.627,1.671c6.448,3.733,7.618,11.962,8.739,19.927c0.646,4.569,1.453,10.253,3.292,12.093 c1.84,1.84,7.524,2.646,12.096,3.292c7.96,1.127,16.189,2.291,19.919,8.745c3.687,6.36,0.619,14.007-2.344,21.404
    c-1.824,4.563-3.898,9.735-3.201,12.347c0.641,2.395,5.079,5.864,8.643,8.657c6.104,4.774,13.025,10.189,13.025,17.799 c0,7.612-6.921,13.025-13.03,17.804c-3.559,2.788-8.002,6.264-8.638,8.654c-0.702,2.614,1.375,7.783,3.201,12.347 c2.964,7.399,6.032,15.046,2.344,21.409c-3.733,6.448-11.959,7.618-19.924,8.739c-4.566,0.646-10.256,1.453-12.09,3.292 c-1.845,1.84-2.646,7.524-3.298,12.096c-1.119,7.96-2.291,16.189-8.745,19.919c-1.909,1.113-4.147,1.677-6.627,1.677 c-4.745,0-9.839-2.048-14.768-4.021c-4.091-1.637-8.315-3.335-11.19-3.335c-0.446,0-0.836,0.048-1.161,0.134 c-2.392,0.635-5.861,5.073-8.648,8.638C138.027,238.464,132.615,245.385,125.003,245.385z" />
</svg>

运用fill-rule="evenodd"的星星和太阳的效果就和刚才的不一样了:

同样用一张图来描述,可能更易于理解:

evenodd规则是特定的算法,与nonzero情况不同,其算法和内部形状绘制的方向不相关,因为只是简单地计算它们穿过直线的路径数是不是奇偶数。

有关于fill-rule更详细的介绍,可以阅读SVG之旅系列中的《填充特性》一文。

clip-path可使用的场景

前面花了很长的一个篇幅介绍了Masking模块中Clipping部分,即可clip-pathclipPathclip-rule三个部分。在实际生产中,使用clip-path可以让我做很多事情,特别是实现以前我们认为无法使用CSS来实现的一些效果。比如:

Web面包屑的效果

比如@Silvestar Bistrović写的一个Demo

详细的介绍可以阅读《Oh, the Many Ways to Make Triangular Breadcrumb Ribbons!》一文。

提示框Tooltip效果

详细的介绍可以阅读 @Sebastiano Guerriero的《Creating rounded triangles in CSS with clip-path》一文。

CSS实现对角容器

@Sebastiano Guerriero把这个效果封装成了一个UI组件(Diagonal Hero Component),它是Hero Component组件的扩展。

除了上面这几个效果之外,还有很多其他的效果,比如:

小结

本文主要介绍了Clipping中的相关特性。着重介绍了CSS的clip-path属性。从上文中我们可以得知,使用clip-path除了可以帮助我们绘制一些基本的图形之外,还可以绘制一些复杂的多边形,特别是配合<clipPath>之后,可以绘制更多的图形。

使用clip-path可以帮助我们实现剪切的效果,它主要会剪切掉路径(或基本图形)之外的区域,简单地说:路径内可见,路径外不可见。这也就是常说的 外剪切,将和拉下来要介绍的Masking还是有很大的不同。如果你感兴趣的话,可以关注下一节的相关内容。

虽然,本文主要介绍如何使用clip-path来实现剪切,但在CSS中,clip-path除了实现Clipping的效果之外,还可以配合CSS Shapes和路径动画(offset-path)实现一些排版和动画效果。但这两部分并不是本文所要阐述的部分,感兴趣的话可以阅读小站以前更新的相关文章。

接下来,我们会着重介绍Masking相关的特性... (^_^)