4

CSS mask 实现鼠标跟随镂空效果

 2 years ago
source link: https://segmentfault.com/a/1190000040996523
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

偶然在某思看到这样一个问题,如何使一个div的部分区域变透明而其他部分模糊掉?,最后实现效果是这样的

237330258-6181fcdb471cf

进一步,还能实现任意形状的镂空效果

Kapture 2021-11-20 at 13.44.26

鼠标经过的地方清晰可见,其他地方则是模糊的。

可能一开始无从下手,不要急,可以先从简单的、类似的效果开始,一步一步尝试,一起看看吧。

一、普通半透明的效果

比如平时开发中碰到更多的可能是一个半透明的效果,有点类似于探照灯(鼠标外面的地方是半透明遮罩,看起来会暗一点)。如下:

image-20211117200548416

那先从这种效果开始吧,假设有这样一个布局:

<div class="wrap" id="img">
    <img class="prew" src="https://tva1.sinaimg.cn/large/008i3skNgy1gubr2sbyqdj60xa0m6tey02.jpg">
</div>

那么如何绘制一个镂空的圆呢?先介绍一种方法

其实很简单,只需要一个足够大的投影就可以了,原理如下

image-20211117195737723

这里可以用伪元素::before来绘制,结构更加精简。用代码实现就是

.wrap::before{
  content:'';
  position: absolute;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  left: 50%;
  top: 50%;
  transform: translate(-50%,-50%); /*默认居中*/
  box-shadow: 0 0 0 999vw rgba(0, 0, 0, .5); /*足够大的投影*/
}

可以得到这样的效果

image-20211117200548416

二、借助 CSS 变量传递鼠标位置

按照以往的经验,可能会在 js 中直接修改元素的 style 属性,类似这样

img.addEventListener('mousemove', (ev) => {
    img.style.left = '...';
    img.style.top = '...';
})

但是这样交互与业务逻辑混杂在一起,不利于后期维护。其实,我们只需要鼠标的坐标,在 CSS 中也能完全实现跟随的效果。

这里借助 CSS 变量,那一切就好办了!假设鼠标的坐标是 [--x,--y](范围是[0, 1]),那么遮罩的坐标就可以使用 calc计算了

.wrap::before{
  left: calc(var(--x) * 100%);
  top: calc(var(--y) * 100%);
}

然后鼠标坐标的获取可以使用 JS 来计算,也比较容易,如下

img.addEventListener('mousemove', (ev) => {
    img.style.setProperty('--x', ev.offsetX / ev.target.offsetWidth);
    img.style.setProperty('--y', ev.offsetY / ev.target.offsetHeight);
})

这样,半透明效果的镂空效果就完成了

Kapture 2021-11-17 at 20.26.27

完整代码可以访问: backdrop-shadow (codepen.io)

三、渐变也能实现半透明的效果

除了上述阴影扩展的方式,CSS 径向渐变也能实现这样的效果

绘制一个从透明到半透明的渐变,如下

.wrap::before{
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    background: radial-gradient( circle at center, transparent 50px, rgba(0,0,0,.5) 51px);
}

可以得到这样的效果

image-20211117200548416

然后,把鼠标坐标映射上去就可以了。从这里就可以看出 CSS 变量的好处,无需修改 JS,只需要在CSS中修改渐变中心点的位置就可以实现了

.wrap::before{
  background: radial-gradient( circle at calc(var(--x) * 100% )  calc(var(--y) * 100% ), transparent 50px, rgba(0,0,0,.5) 51px);
}

Kapture 2021-11-18 at 19.51.30

四、背景模糊的效果尝试

CSS 中有一个专门针对背景(元素后面区域)的属性:backdrop-filter。使用方式和 filter完全一致!

backdrop-filter: blur(10px);

下面是 MDN 中的一个示意效果

image-20211119191341911

backdrop-filter是让当前元素所在区域后面的内容模糊,要想看到效果,需要元素本身半透明或者完全透明;而filter是让当前元素自身模糊。有兴趣的可以查看这篇文章: CSS backdrop-filter简介与苹果iOS毛玻璃效果 « 张鑫旭-鑫空间-鑫生活 (zhangxinxu.com)

需要注意的是,这种模糊与背景的半透明度没有任何关系,哪怕元素本身是透明的,仍然会有效果。例如下面是去除背景后的效果 ,整块都是模糊的

image-20211119193956128

如果直接运用到上面的例子会怎么样呢?

1. 阴影实现

在上面第一个例子中添加 backdrop-filter

.wrap::before{
  content:'';
  position: absolute;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  left: 50%;
  top: 50%;
  transform: translate(-50%,-50%); /*默认居中*/
  box-shadow: 0 0 0 999vw rgba(0, 0, 0, .5); /*足够大的投影*/
  backdrop-filter: blur(5px)
}

得到效果如下

Kapture 2021-11-19 at 19.20.57

可以看到圆形区域是模糊的,正好和希望的效果相反。其实也好理解,只有圆形区域才是真实的结构,外面都是阴影,所以最后作用的范围也只有圆形部分

2. 渐变实现

现在在第二个例子中添加 backdrop-filter

.wrap::before{
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    background: radial-gradient( circle at calc(var(--x) * 100% )  calc(var(--y) * 100% ), transparent 50px, rgba(0,0,0,.5) 51px);
      backdrop-filter: blur(5px)
}

Kapture 2021-11-19 at 19.31.22

已经全部都模糊了,只是圆形区域外暗一些。由于::before的尺寸占据整个容器,所以整个背后都变模糊了,圆形外部比较暗是因为半透明渐变的影响。

总之还是不能满足我们的需求,需要寻求新的解决方式。

五、CSS MASK 实现镂空

与其说是让圆形区域不模糊,还不如说是把那块区域给镂空了。就好比之前是一整块磨砂玻璃,然后通过 CSS MASK 打了一个圆孔,这样透过圆孔看到后面肯定是清晰的。

可以对第二个例子稍作修改,通过径向渐变绘制一个透明圆,剩余部分都是纯色的遮罩层,示意如下

image-20211120113029155

用代码实现就是

.wrap::before{
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    -webkit-mask: radial-gradient( circle at calc(var(--x, .5) * 100% )  calc(var(--y, .5) * 100% ), transparent 50px, #000 51px);
      background: rgba(0,0,0,.3);
      backdrop-filter: blur(5px)
}

这样就实现了文章开头的效果

237330258-6181fcdb471cf

完整代码可以查看:backdrop-mask (codepen.io)

六、CSS MASK COMPOSITE 实现更丰富的镂空效果

除了使用径向渐变绘制遮罩层以外,还可以通过 CSS MASK COMPOSITE(遮罩合成)的方式来实现。标准关键值如下(firefox支持):

/* Keyword values */
mask-composite: add; /* 叠加(默认) */
mask-composite: subtract; /* 减去,排除掉上层的区域 */
mask-composite: intersect; /* 相交,只显示重合的地方 */
mask-composite: exclude; /* 排除,只显示不重合的地方 */

遮罩合成是什么意思呢?可以类比 photoshop 中的形状合成,几乎是一一对应的

image-20211120123004278

-webkit-mask-composite 与标准下的值有所不同,属性值非常多,如下(chorme 、safari 支持)

-webkit-mask-composite: clear; /*清除,不显示任何遮罩*/
-webkit-mask-composite: copy; /*只显示上方遮罩,不显示下方遮罩*/
-webkit-mask-composite: source-over; 
-webkit-mask-composite: source-in; /*只显示重合的地方*/
-webkit-mask-composite: source-out; /*只显示上方遮罩,重合的地方不显示*/
-webkit-mask-composite: source-atop;
-webkit-mask-composite: destination-over;
-webkit-mask-composite: destination-in; /*只显示重合的地方*/
-webkit-mask-composite: destination-out;/*只显示下方遮罩,重合的地方不显示*/
-webkit-mask-composite: destination-atop;
-webkit-mask-composite: xor; /*只显示不重合的地方*/

是不是一脸懵?这里做了一个对应的效果图,如果不太熟练,使用的时候知道有这样一个功能,然后对着找就行了

image-20211120130421281

回到这里,可以绘制一整块背景和一个圆形背景,然后通过遮罩合成排除(mask-composite: exclude)打一个孔就行了,实现如下

.wrap::before{
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    -webkit-mask: url("data:image/svg+xml,%3Csvg width='50' height='50' viewBox='0 0 50 50' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='25' cy='25' r='25' fill='%23C4C4C4'/%3E%3C/svg%3E"), linear-gradient(red, red);
      -webkit-mask-size: 50px, 100%;
      -webkit-mask-repeat: no-repeat;
      -webkit-mask-position: calc(var(--x, .5) * 100% + var(--x, .5) * 100px - 50px )  calc(var(--y, .5) * 100% + var(--y, .5) * 100px - 50px ), 0;
      -webkit-mask-composite: xor;   /*只显示不重合的地方, chorem 、safari 支持*/
      mask-composite: exclude; /* 排除,只显示不重合的地方, firefox 支持 */
      background: rgba(0,0,0,.3);
      backdrop-filter: blur(5px)
}

需要注意-webkit-mask-position中的计算,这样也能很好的实现这个效果

237330258-6181fcdb471cf

完整代码可以查看:backdrop-mask-composite (codepen.io)

你可能已经发现,上述例子中的圆是通过 svg 绘制的,还用到了遮罩合成,看着好像更加繁琐了。其实呢,这是一种更加万能的解决方式,可以带来无限的可能性。比如我需要一个星星⭐️的镂空效果,很简单,先通过一个绘制软件画一个

image-20211120131056453

然后把这段 svg 代码转义一下,这里推荐使用张鑫旭老师的SVG在线压缩合并工具

image-20211120131335734

替换到刚才的例子中就可以了

.wrap::before{
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
    -webkit-mask: url("data:image/svg+xml,%3Csvg width='96' height='91' viewBox='0 0 96 91' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M48 0l11.226 34.55h36.327l-29.39 21.352L77.39 90.45 48 69.098 18.61 90.451 29.837 55.9.447 34.55h36.327L48 0z' fill='%23C4C4C4'/%3E%3C/svg%3E"), linear-gradient(red, red);
      -webkit-mask-size: 50px, 100%;
      -webkit-mask-repeat: no-repeat;
      -webkit-mask-position: calc(var(--x, .5) * 100% + var(--x, .5) * 100px - 50px )  calc(var(--y, .5) * 100% + var(--y, .5) * 100px - 50px ), 0;
      -webkit-mask-composite: xor;   /*只显示不重合的地方, chorem 、safari 支持*/
      mask-composite: exclude; /* 排除,只显示不重合的地方, firefox 支持 */
      background: rgba(0,0,0,.3);
      backdrop-filter: blur(5px)
}

星星镂空实现效果如下

Kapture 2021-11-20 at 13.35.28

完整代码可以查看:backdrop-star (codepen.io)

再比如一个心形❤,实现效果如下

Kapture 2021-11-20 at 13.44.26

完整代码可以查看:backdrop-heart (codepen.io)

只有想不到,没有做不到

七、总结和说明

以上实现了一个鼠标跟随镂空的效果,从简单到复杂,从单一到通用,虽然借助了一点点 JS ,但是仅仅是“工具人”的角色,交互逻辑全部都由 CSS 完成,下面总结一下:

  1. 足够大的阴影是一个实现圆形镂空效果的小技巧
  2. CSS 渐变也能轻易的绘制出圆形镂空背景
  3. 借助 CSS 变量可以很方便的利用鼠标位置实现想要的效果
  4. backdrop-filter 可以想象成磨砂玻璃的功能
  5. CSS Mask 可以给磨砂玻璃打孔,实现镂空的效果
  6. 借助遮罩合成特性和SVG,可以实现任意形状的镂空效果

CSS MASK 还是非常强大的,有必要还是多掌握一下。最后,如果觉得还不错,对你有帮助的话,欢迎点赞、收藏、转发❤❤❤


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK