CSS MASK 实现鼠标跟随镂空效果
之前在某社区看到这样过一个问题,如何使一个div的部分区域变透明而其他部分模糊掉?[1],最后的实现效果如下
进一步,还能实现任意形状的镂空效果(微信压缩的有点过分了,建议查看 demo)
特点是鼠标经过的地方清晰可见,其他地方则是模糊的。
可能很多同学一开始无从下手,没有思路,没关系,不要急,可以先从简单的、类似的效果开始,一步一步尝试,一起看看吧。
一、普通半透明效果
平时开发中可能还会碰到一种半透明的效果,有点类似于探照灯(鼠标之外是半透明遮罩,看起来会暗一点),效果如下:
和文章开头有点类似吧?那先从这种效果开始,假设布局是这样的:
<div class="wrap">
<img class="prew" src="https://tva1.sinaimg.cn/large/008i3skNgy1gubr2sbyqdj60xa0m6tey02.jpg">
</div>
那么如何绘制一个中间镂空的圆呢?下面介绍两种方法
1. 阴影扩展实现
其实很简单,需要一点点想象力,一个足够大的阴影就可以了,原理如下
为了结构更加精简,这里可以用伪元素::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); /*一个足够大的投影*/
}
这样就得到了以下效果
2. 径向渐变实现
除了上述阴影扩展的方式,利用 CSS 径向渐变 也能实现这样的效果
需要绘制一个从透明到半透明的渐变,关键代码如下
.wrap::before{
/**/
background: radial-gradient( circle at center, transparent 50px, rgba(0,0,0,.5) 51px);
}
效果如下
二、借助 CSS 变量传递鼠标位置
按照之前的经验,可能会在 js 中直接修改目标元素的 style 属性,比如像这样的操作
img.addEventListener('mousemove', (ev) => {
img.style.left = 'XXpx';
img.style.top = 'XXpx';
})
虽然也能实现想要的效果,但是这样交互与业务逻辑混合在一起,不利于后面的维护。其实,我们只需要通过 js 拿到鼠标的坐标就行了(CSS没有这样的能力),然后剩下的在 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);
})
这样一来,半透明效果的镂空效果就完成了,实时效果如下(注意观察--x 的变化)
完整代码可以访问:backdrop-shadow (codepen.io)[2]
再来说第二种径向渐变的方案。
从这里就可以看出 CSS 变量的好处了,无需修改 JS,只需要在CSS中修改渐变中心点的位置就可以实现了(at 关键词),关键代码如下
.wrap::before{
background: radial-gradient( circle at calc(var(--x) * 100% ) calc(var(--y) * 100% ), transparent 50px, rgba(0,0,0,.5) 51px);
}
三、背景模糊尝试
在 CSS 中,有一个专门针对背景(元素背后区域)的滤镜:backdrop-filter[3]。
backdrop-filter: blur(10px);
下面是 MDN 中的一个示意效果
backdrop-filter
是让当前元素所在区域后面的内容模糊,要想看到效果,需要元素本身半透明或者完全透明;而filter
是让当前元素自身模糊。有兴趣的可以查看这篇文章:CSS backdrop-filter简介与苹果iOS毛玻璃效果[4]
现在尝试一下
1. 阴影扩展实现
如果在上面第一个例子中添加 backdrop-filter
.wrap::before{
/**/
left: 50%;
top: 50%;
transform: translate(-50%,-50%); /*默认居中*/
box-shadow: 0 0 0 999vw rgba(0, 0, 0, .5); /*足够大的投影*/
backdrop-filter: blur(5px)
}
实时效果如下
可以看到鼠标区域是模糊的,外面是清晰的,正好和希望的效果相反。原因是,圆形区域才是真实的结构,外面都是阴影,是不存在的,所以最后滤镜的作用范围只有圆形部分
2. 径向渐变实现
如果在第二个例子中添加 backdrop-filter
.wrap::before{
/**/ 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)
}
实时效果如下(微信压缩有点厉害)
可惜的是,已经全部都模糊了,只是鼠标区域外面暗一些。这是由于::before
的尺寸占据整个容器,所以整个背后都变模糊了,圆形外部比较暗是因为半透明渐变的影响。
四、CSS MASK 实现镂空效果
MASK 的原理和现实生活比较像,你可以想象成镂空,就好比一整块磨砂玻璃,然后通过 CSS MASK 打了一个圆孔,这样透过圆孔看到后面肯定是清晰的。
然后对第二个例子稍作修改,把这个径向渐变作为层,示意如下
用代码实现就是
.wrap::before{
-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)
}
这样就实现了文章开头的效果
完整代码可以查看:backdrop-mask (codepen.io)[5]
五、借助 MASK COMPOSITE 实现更灵活的镂空效果
除了使用径向渐变绘制遮罩层以外,还可以通过 CSS MASK COMPOSITE(遮罩合成)[6]的方式来实现,这种方式更为灵活,可以扩展其他形状的效果。
标准关键值如下(可惜只有firefox支持):
mask-composite: add; /* 叠加(默认值) */
mask-composite: subtract; /* 减去,排除掉上层的区域 */
mask-composite: intersect; /* 相交,只显示重合的地方 */
mask-composite: exclude; /* 排除,只显示不重合的地方 */
MASK COMPOSITE 是什么意思呢?可以类比设计软件中的形状合成,几乎是一一对应的,下面是 photoshop 的效果示意
-webkit-mask-composite[7] 与标准下的值有所不同,属性值非常多(chrome 和 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; /*只显示不重合的地方*/
是不是一脸懵了?其实功能是完全覆盖了标准属性,这里做了一个各个属性对应的效果图,如下
现在回到这里,可以绘制一整块背景(线性渐变实现)和一个圆形背景(径向渐变实现),然后通过遮罩合成排除(mask-composite: exclude
)打一个孔就行了,实现如下
.wrap::before{
-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
中的计算,可以充分利用 calc 的特性,实时效果如下
完整代码可以查看:backdrop-mask-composite (codepen.io)[8]
可能你已经发现,上述例子中的圆是通过 svg 绘制(已经进行 CSS 转义)的,还用到了遮罩合成,看着好像比前面的方式更加繁琐了。
为什么这么做呢?其实,这是一种更加万能的解决方式,可以带来无限的可能性。比如我现在需要一个星星的镂空效果,很简单,先通过一个绘制软件画一个,下面是 Figma
然后把这段 svg 代码转义一下,这里推荐使用张鑫旭老师的SVG在线压缩合并工具[9],然后把刚才圆形的 svg 替换成这个星形的svg 就可以了
.wrap::before{
/**/ -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)
}
下面是星星镂空效果
完整代码可以查看:backdrop-star (codepen.io)[10]
再比如想要一个心形❤,也采用同样的方式,实现效果如下
完整代码可以查看:backdrop-heart (codepen.io)[11]
只要你能想到的,都可以实现,是不是非常神奇?
六、简单总结一下
以上实现了一个鼠标跟随镂空的效果,从简单到复杂,从单一到通用,虽然借助了一点点 JS ,但是仅仅是“工具”的角色,交互逻辑全部都由 CSS 完成,而且根据灵活,适应性更强,下面总结一下要点:
足够大的阴影是一个实现圆形镂空效果的小技巧 CSS 渐变也能轻易的绘制出圆形镂空背景 借助 CSS 变量可以很方便的利用鼠标位置实现想要的效果 backdrop-filter 可以想象成磨砂玻璃的功能 CSS Mask 可以给磨砂玻璃打孔,实现镂空的效果 借助遮罩合成特性和SVG,可以实现任意形状的镂空效果
CSS MASK 还是非常强大的,有必要还是多掌握一下。最后,如果觉得还不错,对你有帮助的话,欢迎点赞、收藏、转发❤❤❤
参考资料
如何使一个div的部分区域变透明而其他部分模糊掉?: https://segmentfault.com/q/1010000040902161
[2]backdrop-shadow (codepen.io): https://codepen.io/xboxyan/pen/VwzRaNZ
[3]backdrop-filter: https://developer.mozilla.org/zh-CN/docs/Web/CSS/backdrop-filter
[4]CSS backdrop-filter简介: https://www.zhangxinxu.com/wordpress/2019/11/css-backdrop-filter/
[5]backdrop-mask (codepen.io): https://codepen.io/xboxyan/pen/porpoXJ
[6]CSS MASK COMPOSITE(遮罩合成): https://developer.mozilla.org/en-US/docs/Web/CSS/mask-composite
[7]-webkit-mask-composite: https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-mask-composite
[8]backdrop-mask-composite (codepen.io): https://codepen.io/xboxyan/pen/ExvMpQB
[9]SVG在线压缩合并工具: https://www.zhangxinxu.com/sp/svgo/
[10]backdrop-star (codepen.io): https://codepen.io/xboxyan/pen/vYJPaVy
[11]backdrop-heart (codepen.io): https://codepen.io/xboxyan/pen/KKvEBjb