Clipping和Masking 何时使用

发布于 大漠

前面花了很长的篇幅在《探索CSS Masking模块:Clipping》和《探索CSS Masking模块:Masking》两文中分两部分详细介绍了CSS Masking Module Level 1中的ClippingMasking的基础知识和相关特性,并且使用一些简单的小示例向大家演示了Clipping和Masking如何使用。记得在介绍Clipping一文中,抛出实际项目中的一个案例,实现一个类似下图镂空的效果:

如果是你,你会怎么做呢?

常规的CSS方法

我写了个小示例,尝试着使用了不同的几种方法来做上面的效果:

分别采用了 box-shadowborderradial-gradient。虽然这几种方式都能实现想要的效果,但每种方式都有其局限性:

  • box-shadowboder对于不规则图形的镂空,实现难度较大
  • radial-gradient配合linear-gradient以及多背景可以实现一些常见图形的镂空效果,但对于较为复杂的同样是较难实现

另外,这几种方式原里都非常简单,借助一个伪元素::after::after来做:

.box-shadow::after {
    content: '';
    width: 60px;
    height: 60px;
    background: rgba(0,0,0,0);
    position: absolute;
    top: 276px;
    right: 2px;
    border-radius: 100%;
    box-shadow: 0 0 0 99999rem rgba(0,0,0,.65);
}

使用box-shadow的第四个参数,使用一个较大的阴影来实现半透明区域。

.border::after {
    content: '';
    width: 60px;
    height: 60px;
    background: rgba(0,0,0,0);
    position: absolute;
    top: -222px;
    right: -500px;
    border-radius: 100%;
    border: 500px solid rgba(0,0,0,.65);
}

borderbox-shadow类似,不同的是采用了一个很粗的边框来实现半透明区域。这两种方式都会让伪元素溢出容器,所以需要在容器上设置overflow:hidden

再来看radial-gradient的实现方式:

.radial-gradient::after {
    content: '';
    background: radial-gradient(circle at 368px 308px, transparent, transparent 30px, rgba(0,0,0,.65) 30px, rgba(0,0,0,.65) 100%);
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    left: 0;
}

相对而言,radial-radient灵活性更好,但要掌握如何使用渐变来绘制图形,这就需要你对CSS的渐变相关的知识有较深的了解。如果你对这方面感兴趣的话,你可以阅读下面几篇文章:

似乎这个跟我们今天要聊的主题没有太大关系一样,事实只是拿这个示例做为一个引子,做一个抛砖引玉的效果。

Clipping 和 Masking能做什么

从某种意义上来说,Clipping和Masking都可以让元素某些部分可见和不可见。简单地说:

Clipping用于外切(被切的部分不可见);Masking用于内切(内部可见),对于Masking而言一切取决于遮罩图形

两者区别之处:

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

Clipping 和 Masking何时使用

那么在实际使用当中,Clipping和Masking应该怎么去选择运用场景呢?好比文章开头提到的案例,实现类似上图镂空的效果,是应该使用Clipping呢?还是Masking呢?

从效果上看,是在一个黑色半透明层(比如rgba(0,0,0,.65))上挖去一个大约64px x 64px的圆形,而且这个圆形刚好在对应的Icon位置。对应前面所学的Clipping和Masking相关的知识,要实现这样的一个镂空效果,我们应该使用Masking来实现,因为它是一个内切。

接下来,我们来看怎么使用Masking相关的知识来实现这样的效果。

首先有一个Web页面,好比下图这样的效果:

接着在上面盖了一个层,比如使用一个伪元素实现:

Masking中有两个遮罩模式,高亮模式和Alpha模式:

根据遮罩模式的相关原理,我们需要的遮罩图应该像下面这样的:

不管是Alpha模式还是高亮模式,使用mask相关属性即可将伪元素所需要的地方进行镂空:

.mask-alpha,
.mask-luminance{
    &::after {
        mask: radial-gradient(circle at 368px 308px, transparent,transparent 30px, rgba(0, 0, 0, 1) 30px, rgba(0, 0, 0, 1) 100%) no-repeat;
        mask-size: 440px 688px;
    }
}

你将看到的效果如下:

看上去和文章最开始使用的渐变实现的效果是一样的,只不过将渐变图像换成遮罩图像。或许很多同学会认为这样做,不是把事情变得更复杂了,既然渐变就可以实现,为什么还需要采用遮罩来呢?事实上,采用遮罩还是有其优势之处的。比如,你要的效果是不是一个圆,是其他不规则的图形。那么我们就可以借助SVG或者PNG图来做为遮罩图。还是拿这个为例,我们有一个遮罩图像:

这是一张带有Alpha通道的遮罩图:

如果直接将上面的遮罩图运用于案例中,你会看到的效果如下:

这离我们想要的实际效果相关甚完。就算是我们换过一张图:

能看到镂空效果,但和我们所需还是甚远:

换张大图肯定能达到我们想要的效果,但这又回到以前使用背景图片的年代,即可扩展性,可维护性不好

接着,我们换过一种思维,采用两张图(即两张遮罩图),比如前面那个带Alpha通道的遮罩图,再配一张全黑全白的图(使用CSS渐变绘制出来的),再结合Masking的合成计算来达到我们想要的效果,把上面的示例修改一下:

.mask{

    background: url('https://static.fedev.cn/sites/default/files/blogs/2019/1905/mask-clip-path-1.png') no-repeat;
    background-size: 400px 688px;
    position: relative;

    &::after {
        content: '';
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        background-color: rgba(0,0,0,.65);
        mask-image: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/217233/mask.png'), linear-gradient(to bottom, #fff, #fff);
        mask-repeat:no-repeat, repeat;
        mask-position: 338px 274px, 0 0;
        mask-size: 64px 64px;
        mask-composite: exclude;
        mask-composite: xor;
    }
}

你将看到的效果如下:

@Jamie Coulter在Codepen上使用mask做了另一个有意思的遮罩效果,效果如下,在区域移动鼠标,能看到清晰的内容:

CSS Maksing除来实现上面这种镂空和探照灯的效果之外,还能实现什么样的效果呢?或者说使用场景?

记得在《Web技巧(06)》一文中向大家展示如何使用CSS的特性来修改Icon图标的颜色,而且其中给大家演示了如何使用mask来修改图标颜色。

假设我们有一个黑色的图标:

借助CSS的mask可以很轻易的修改Icon图标的颜色:

既可以实现纯色的Icon,还可以实现渐变色的Icon:

.mask {
    mask: url(https://static.fedev.cn/sites/default/files/blogs/2019/1905/icon-bell-black.svg) no-repeat center;
    mask-size: cover;
    background-color: #f36;
}

.mask-gradient {
    mask: url(https://static.fedev.cn/sites/default/files/blogs/2019/1905/icon-bell-black.svg) no-repeat center;
    mask-size: cover;
    background: linear-gradient(to right, #f36, #de9);
}

在互动项目中,为了营造一些氛围效果,文本会有一些渐变填充的效果。早前实现该效果一般都会使用:

background-image: linear(to right, #f36, #ef9);
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;

如果背景是图片的话,还可以实现图片填充文本的效果:

有了mask相关特性之外,也可以实现文本填充的效果,只不过要有较好的纹理或渐变填充,该方法相对较难一些,但可以实现一些其他的效果,比如下面这个示例:

其实,在CSS的mask中还可以引用SVG的<mask>元素来作为遮罩源,使用这种技术也可以实现文本填充的效果,只不过目前只有Firefox浏览器才支持。我Fork了@Preethi Sam写的一个示例

<div class="backdrop">
    <p class='text'></p>
</div>

<svg>
    <defs>
        <mask  id="m" >
            <rect width="100%" height="100%" fill="white" />
            <text x="50%" y="75%" text-anchor="middle">Taitō</text>
        </mask >
    </defs>
</svg>

CSS代码如下:

.backdrop {
    background: linear-gradient(to right, #f36, #90f, #ed0, #0ff, #ad3) center;
    width:80vw;
    margin: 0 auto;
}
.text {
    height:20vw;
    margin: 0;
    font: bolder 10vw 'Alfa Slab One';
    background: linear-gradient(to right,#ad3, #0ff, #ed0,  #90f,  #f36);
    mask-type: luminance;
    mask: url("#m");              
}
svg{
    width: 80vw;
    height: 20vw;
}
text{
    font:bolder 8vw 'Alfa Slab One';
}

效果如下:

如果需要查看在线Demo的效果,请使用Firefox浏览器点击这里查看

如果你对文本填充的一些效果感兴趣,可以阅读下面这些教程:

除了上面提到的这些效果之外,还可以实现一些其他的效果,比如底部淡入遮罩效果:

上图的效果来自于 @Fernanda Parisi写的一个Demo

配合CSS的animation特性,还可以做一些有趣的动效,比如 @Shak Daniel写的这个Demo效果

在介绍Masking特性的时候,还向大家演示了如何借助mask-border特性和mask特性来实现优惠卷的效果:

要是配合多个mask还可以轻易实现下图这样的效果:

有关于实现这个内凹角更详细的介绍,可以阅读@ANA TUDOR的《Scooped Corners in 2018》一文。中文介绍可以点击这里

前面主要聊了一下,CSS的Masking可运用的场景,接着我们来简单的聊聊Clipping可运用的场景。从Clipping一文中,我们知道CSS的clip-path可以绘制一些基本图形或者较为复杂的多边形,比如Clippy提供的这些效果:

也就是说,对于一些需要不规则图形的场景之下,都可以使用Clipping相关的特切,比如Clipping一文中演示的示例:

除了这些效果之外,还可以实现其他的一些效果,比如结合SVG的<clipPath>也可以轻易实现文本填充的效果,比如下面这个效果:

你也可以发挥你的创意,像 @Christopher Kirk-Nielsen 一样使用clip-path整出类似卡通图的动效

接着再来看几个实际项目中会用到的,比如下图这样的效果:

使用 clip-path就可以很容易实现:

.notch {
    --notchSize: 2em;
    
    clip-path:
        polygon(
            0% var(--notchSize),
            var(--notchSize) 0%,
            calc(100% - var(--notchSize)) 0%,
            100% var(--notchSize),
            100% calc(100% - var(--notchSize)),
            calc(100% - var(--notchSize)) 100%,
            var(--notchSize) 100%,
            0% calc(100% - var(--notchSize))
        );
}

如果你的产品说我需要用户的头像是不规则的,那clip-path的优势就来了:

更有意思的是,在CSS Shapes中也会用到clip-path属性,可以让你实现很多不规则的Web布局效果,比如:

还可以配合CSS的offset-path特性实现一些路径动效(CSS Motion Path)。比如 @一丝姐姐写的 支付宝芝麻信用动画效果:

小结

到这为止,有关于CSS Masking Module Level1相关的特性就算是介绍完了,有关于该特性中涉及理论知识部分主要在《探索CSS Masking模块:Masking》和《探索CSS Masking模块:Clipping》中花了较长的篇幅阐述完了。为了验证CSS Masking特性的可用性,从实际项目进行展开,并且深度扩展开,探讨了CSS中Clipping和Masking在实际生产中的一些使用场景。最后希望这篇文章对大家有所帮助,希望大家能尝试着将该特性用于生产中,提高更可用性。如果您在这方面有相关经验和建议,欢迎在下面的评论中一起分享。