“蒙”出花样!用 CSS Mask 实现丝滑视觉魔法

引言

在前端开发中, CSS 不再仅仅用于布局和样式修饰, 越来越多的高级视觉效果也可以通过纯 CSS 实现, 其中就包括令人惊艳的 蒙版 效果。mask-image 作为 CSS 中用于控制元素可见区域的强大属性, 能够帮助开发者实现类似 Photoshop 中的遮罩操作, 无需借助复杂的图像处理。无论是实现渐隐文字、柔和的图像遮罩, 还是动态的 手电筒 追光效果, mask-image 都提供了灵活而优雅的解决方案。本文将深入介绍 mask-image 的基本用法、支持的各种类型(如渐变、SVG)、配套属性 (如 mask-mode), 并结合多个实战示例, 带你全面掌握 CSS 蒙版的使用技巧。

一、基本语法

mask-imageCSS 中用来定义蒙版效果的一个属性。它可以根据一个图像或渐变、控制 元素 的可见区域, 实现类似 Photoshop 中的蒙版功能。

如下代码所示, background-image 所支持的 mask-image 都可以适用, 并且类似 background 系列属性, mask-* 中也都有对应的属性, 用于设置蒙版图参数

html 复制代码
<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 我们这边也是可以使用渐变的:

html 复制代码
<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') 方式进行了引用。

html 复制代码
<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>, 来设置模版作用模式, 如下代码所示

html 复制代码
<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 超出部分需要隐藏
  • 但是如果直接隐藏容器底部过渡就特别生硬
  • 如下代码所示:
html 复制代码
<div className="container">
  <p>
    我经常遇到两种候选人。一种是一听算法题, 就两手一摊, 表情痛苦, 说"哥, 我天天写业务, 真没准备这个"。另一种呢, 正好相反, 题目一出, 眼睛一亮, 不出三十秒, 就把 LeetCode 上背得滚瓜烂熟的最优解, 一字不差地敲了出来, 然后一脸期待地看着我。
    说实话, 这两种, 都不是我最想看到的。
  </p>
  <p>
    这就引出了一个很多候选人都想问, 但不敢问的问题:"你们这些面试官, 到底怎么想的?你们明知道我们前端平时工作中, 99%的时间都用不上这些, 为什么非要折磨我们?"
    今天, 我就想站在桌子对面, 跟大伙掏心窝子地聊聊, 我们问算法题, 到底图个啥。
  </p>
</div>
<style>
  .container {
    width: 400px;
    color: #999;
    max-height: 200px;
    overflow: hidden;
  }
</style>

最后效果如下, 容器底部文字硬生生的被切断了

而更好的效果应该是有个完美的过渡效果, 这里我们就可以使用蒙版来处理: 如下代码所示, 蒙版是一个渐变, 从下到上, 渐变透明度从 0 ~ 100 一个过渡

diff 复制代码
<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>

而最终效果如下: 整个过渡还是很丝滑的

然而有些站点为了实现上述过渡效果, 简单粗暴的在容器底部覆盖了一层渐变背景图! 而如此实在不够优雅, 如果页面背景复杂的话就完全没有效果!

但是用我们的方式肯定就能应付上面这情况了。

下面看另一个例子, 有代码如下:

html 复制代码
<div className="container">
  我经常遇到两种候选人。一种是一听算法题, 就两手一摊, 表情痛苦, 说"哥, 我天天写业务, 真没准备这个"。另一种呢,
  正好相反, 题目一出, 眼睛一亮, 不出三十秒不出三十秒, 就把 LeetCode 上背得滚瓜烂熟的最优解, 一字不差地敲了出来,
  然后一脸期待地看着我。
</div>
<style>
  .container {
    width: 400px;
    color: #999;
    max-height: 200px;
    overflow: hidden;
  }
</style>

效果如下, 但是我们希望在第二行行末, 有个渐隐的效果:

下面我们直接改代码:

html 复制代码
<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>

最终效果:

而这里实际上使用了椭圆渐变来实现蒙版, 我们可以把蒙版改为背景, 来看下蒙版图片的样子:

diff 复制代码
.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: 手电筒

你也许看到过下图类似的一个效果, 这看起来也许很唬人, 但是实际上了解了蒙版的概念后, 实现这么一个效果还是很简单的:

如下代码所示:

  1. 我们只需要通过 onMouseMoveonMouseLeave 来记录鼠标移动位置
  2. 并将鼠标位置存储下来, 转为 CSS 变量 --client-x 以及 --client-y
  3. 最后使用动态的 CSS 变量来渲染蒙版即可
js 复制代码
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`,
      }}
    />
  );
};
css 复制代码
.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 控制蒙版图片的位置, 从而实现过渡的转场效果:

css 复制代码
.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 制作转场动画

九、参考文档

相关推荐
huabuyu2 小时前
基于 React + MarkdownIt 的 Markdown 渲染器实践:支持地图标签和长按复制
前端
芦苇Z2 小时前
HTML <a> 标签的 rel 属性全解析:安全、隐私与 SEO 最佳实践
前端·html
在这儿不行2 小时前
Android 15边到边模式
前端
源猿人2 小时前
企业级文件浏览系统的Vue实现:架构设计与最佳实践
前端·javascript·数据可视化
红红大虾2 小时前
Defold引擎中关于CollectionProxy的使用
前端·游戏开发
最后一个农民工2 小时前
vue3实现仿豆包模版式智能输入框
前端·vue.js
xw53 小时前
uni-app中v-if使用”异常”
前端·uni-app
!win !3 小时前
uni-app中v-if使用”异常”
前端·uni-app