“蒙”出花样!用 CSS Mask 实现丝滑视觉魔法
引言
在前端开发中, CSS
不再仅仅用于布局和样式修饰, 越来越多的高级视觉效果也可以通过纯 CSS
实现, 其中就包括令人惊艳的 蒙版
效果。mask-image
作为 CSS
中用于控制元素可见区域的强大属性, 能够帮助开发者实现类似 Photoshop
中的遮罩操作, 无需借助复杂的图像处理。无论是实现渐隐文字、柔和的图像遮罩, 还是动态的 手电筒
追光效果, mask-image
都提供了灵活而优雅的解决方案。本文将深入介绍 mask-image
的基本用法、支持的各种类型(如渐变、SVG
)、配套属性 (如 mask-mode
), 并结合多个实战示例, 带你全面掌握 CSS
蒙版的使用技巧。
一、基本语法
mask-image
是 CSS
中用来定义蒙版效果的一个属性。它可以根据一个图像或渐变、控制 元素
的可见区域, 实现类似 Photoshop
中的蒙版功能。
如下代码所示, background-image
所支持的 值
在 mask-image
都可以适用, 并且类似 background
系列属性, mask-*
中也都有对应的属性, 用于设置蒙版图参数
<div className="wrapper" />
<style>
.wrapper {
width: 500px;
height: 500px;
// 设置背景图
background-image: url('./bg.png');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
// 设置蒙版
mask-image: url('./mask_alpha.png');
mask-size: 100%;
mask-repeat: no-repeat;
mask-position: center;
}
</style>
如下图是上面代码效果, 默认情况下
遮罩图像的 Alpha(透明度)
的值将会作用于 元素
:
- 透明度为
100%
的区域, 则会完全展示对应元素内容 - 透明度为
0%
的区域, 则会完全隐藏对应元素内容
二、 使用渐变
同 background-image
我们这边也是可以使用渐变的:
<div className="wrapper" />
<style>
.wrapper {
width: 500px;
height: 500px;
// 设置背景图
background-image: url('./bg.png');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
// 设置渐变蒙版
mask-image: linear-gradient(to bottom, rgb(255 0 0 / 0%) 0%, rgb(255 0 0 / 100%) 100%);
}
</style>
如上代码, mask-image
设置为一个渐变效果, 整个渐变只是在透明度上发生变化, 而最终效果如下:
三、 使用 SVG
注意不同于 background-image
, mask-image
还可以设置为某个 svg
上的 <mask/>
。如下代码所示我们在 svg
中定义了一个 <mask/>
并在 mask-image
中通过 url('#mask')
方式进行了引用。
<div className="wrapper" />
<svg viewBox="-10 -10 300 300">
<mask id="mask">
<ellipse
cx="50%"
cy="50%"
rx="25%"
ry="25%"
fill="white"
/>
</mask>
</svg>
<style>
.wrapper {
width: 500px;
height: 500px;
// 设置背景图
background-image: url('./bg.png');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
// 设置渐变蒙版
mask-image: url('#mask');
}
</style>
而最终的效果如下:
四、可应用于任意元素
我们先将上文用到的蒙版图片转为 base64
数据
下面我们在掘金上进行尝试, 在控制台直接在 body
上设置蒙版:
上面例子是为了说明蒙版可作用于任何元素, 可直接将任意元素部分 蒙
住
五、 mask-mode: 设置蒙版规则
上文我们提到, 默认情况下
遮罩图像的 Alpha(透明度)
的值将会作用于 元素
:
- 透明度为
100%
的区域, 则会完全展示对应元素内容 - 透明度为
0%
的区域, 则会完全隐藏对应元素内容
之所以如此是因为 mask-mode
默认值为 match-source
即模版作用规则由源决定, 这里就两种情况:
- 如果
mask-image
引用的是SVG
中的<mask>
, 则使用其mask-type
属性值(如果存在)。如果未明确设置, 则此值默认为Alpha
模式。 - 如果蒙版图片的源是
<image>
或<gradient>
, 则使用蒙版图像的Alpha
值。
那么除了 Alpha
模式之外还有其他的模式吗? 有的, 那就是 luminance(亮度)
, 即根据模版图片不同的亮度来控制元素的显隐:
- 蒙版图片中黑色区域, 其对应位置元素完全透明(不可见)
- 蒙版图片中白色区域, 其对应位置元素完全不透明(可见)
- 蒙版图片中灰色区域, 其对应位置元素半透明
- 蒙版图片中越白部分, 其对应位置元素透明度越高, 反之越黑则透明度越低
而这里我们可通过 mask-mode: <alpha | luminance | match-source>
, 来设置模版作用模式, 如下代码所示
<div className="wrapper" />
<style>
.wrapper {
width: 500px;
height: 300px;
background-image: url("./bg.png");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
// 设置蒙版模式为亮度模式
mask-mode: luminance;
// 蒙版图片为渐变, 即白 => 黑
mask-image: linear-gradient(to bottom, #fff, #000);
mask-size: 100%;
mask-repeat: no-repeat;
mask-position: center;
}
</style>
最终效果如下, 蒙版白色部分元素完全可见, 黑色部分元素不可见, 中间灰色过渡对应的就是元素可见度的过渡
六、 DEMO: 渐变隐藏
我们要实现这么一个效果:
- 我们有个容器, 容器高度是自适应
- 同时容器最大高度默认为
200px
超出部分需要隐藏 - 但是如果直接隐藏容器底部过渡就特别生硬
- 如下代码所示:
<div className="container">
<p>
我经常遇到两种候选人。一种是一听算法题, 就两手一摊, 表情痛苦, 说“哥, 我天天写业务, 真没准备这个”。另一种呢, 正好相反, 题目一出, 眼睛一亮, 不出三十秒, 就把 LeetCode 上背得滚瓜烂熟的最优解, 一字不差地敲了出来, 然后一脸期待地看着我。
说实话, 这两种, 都不是我最想看到的。
</p>
<p>
这就引出了一个很多候选人都想问, 但不敢问的问题:“你们这些面试官, 到底怎么想的?你们明知道我们前端平时工作中, 99%的时间都用不上这些, 为什么非要折磨我们?”
今天, 我就想站在桌子对面, 跟大伙掏心窝子地聊聊, 我们问算法题, 到底图个啥。
</p>
</div>
<style>
.container {
width: 400px;
color: #999;
max-height: 200px;
overflow: hidden;
}
</style>
最后效果如下, 容器底部文字硬生生的被切断了
而更好的效果应该是有个完美的过渡效果, 这里我们就可以使用蒙版来处理: 如下代码所示, 蒙版是一个渐变, 从下到上, 渐变透明度从 0 ~ 100
一个过渡
<style>
.container {
width: 400px;
color: #999;
max-height: 200px;
overflow: hidden;
+ mask-image: linear-gradient(to top, rgb(0 0 0 / 0%), rgb(0 0 0 / 100%) 40px);
}
</style>
而最终效果如下: 整个过渡还是很丝滑的
然而有些站点为了实现上述过渡效果, 简单粗暴的在容器底部覆盖了一层渐变背景图! 而如此实在不够优雅, 如果页面背景复杂的话就完全没有效果!
但是用我们的方式肯定就能应付上面这情况了。
下面看另一个例子, 有代码如下:
<div className="container">
我经常遇到两种候选人。一种是一听算法题, 就两手一摊, 表情痛苦, 说“哥, 我天天写业务, 真没准备这个”。另一种呢,
正好相反, 题目一出, 眼睛一亮, 不出三十秒不出三十秒, 就把 LeetCode 上背得滚瓜烂熟的最优解, 一字不差地敲了出来,
然后一脸期待地看着我。
</div>
<style>
.container {
width: 400px;
color: #999;
max-height: 200px;
overflow: hidden;
}
</style>
效果如下, 但是我们希望在第二行行末, 有个渐隐的效果:
下面我们直接改代码:
<div className="container">
我经常遇到两种候选人。一种是一听算法题, 就两手一摊, 表情痛苦, 说“哥, 我天天写业务, 真没准备这个”。另一种呢,
正好相反, 题目一出, 眼睛一亮, 不出三十秒不出三十秒, 就把 LeetCode 上背得滚瓜烂熟的最优解, 一字不差地敲了出来,
然后一脸期待地看着我。
</div>
<style>
.container {
width: 600px;
color: #999;
line-height: 1.6em;
max-height: 3.2em;
overflow: hidden;
mask-mode: luminance;
mask-image: radial-gradient(ellipse 1000px 300px at 100% 40px, #000, #fff 10%, #fff 10%);;
}
</style>
最终效果:
而这里实际上使用了椭圆渐变来实现蒙版, 我们可以把蒙版改为背景, 来看下蒙版图片的样子:
.container {
width: 600px;
color: #999;
line-height: 1.6em;
max-height: 3.2em;
overflow: hidden;
+ background-image: radial-gradient(ellipse 1000px 300px at 100% 40px, #000, red 10%, red 10%);;
}
如下图所示:
七、 DEMO: 手电筒
你也许看到过下图类似的一个效果, 这看起来也许很唬人, 但是实际上了解了蒙版的概念后, 实现这么一个效果还是很简单的:
如下代码所示:
- 我们只需要通过
onMouseMove
和onMouseLeave
来记录鼠标移动位置 - 并将鼠标位置存储下来, 转为
CSS
变量--client-x
以及--client-y
- 最后使用动态的
CSS
变量来渲染蒙版即可
const MaskImagePage: FC = () => {
const [client, setClient] = useState(HIDE_CLIENT);
const handleMouseMove = useCallback((e) => {
setClient({
x: e.clientX,
y: e.clientY,
});
}, []);
const handleMouseLeave = useCallback(() => {
setClient(HIDE_CLIENT);
}, []);
return (
<div
className="page"
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
style={{
'--client-x': `${client.x}px`,
'--client-y': `${client.y}px`,
}}
/>
);
};
.page {
width: 100vw;
height: 100vh;
background-image: url("./page_bg.png"); // 随便一个背景图, 这个不重要
background-size: 100% 100%;
mask-mode: luminance;
mask-image: radial-gradient(circle at var(--client-x) var(--client-y), #fff, #000 100px);
}
八、DEMO: 惊艳的过渡转场
在 奇妙的 CSS MASK 一文中, 有这么一个效果:
而这里就使用到蒙版, 蒙版图片如下所示, 其实就是好多帧蒙版拼接出来的一个长图:
而关键代码如下, 其实就是通过控制 mask-position
控制蒙版图片的位置, 从而实现过渡的转场效果:
.container {
mask-image: url(https://i.imgur.com/AYJuRke.png);
mask-size: 3000% 100%;
}
@keyframes maskMove {
from {
mask-position: 0 0;
}
to {
mask-position: 100% 0;
}
}
完整 DEMO
查看: mask 制作转场动画