DotGlass:点阵开孔毛玻璃,让模糊只在孔洞里发生
有些视觉效果并不需要“更强”,反而需要“更克制”。
玻璃拟态(Glassmorphism)已经很常见:半透明面板 + 背景模糊,像一块柔软的透镜。但这篇想聊一种更“反直觉”的版本——只有孔洞里才看得见模糊,其他区域是纯色盖板,把背后的内容安静地挡住。
我把它封装成了一个组件:DotGlass(点阵开孔毛玻璃)。它适合做一些微妙的纹理、遮罩或信息层级:既有材质感,也不过度喧宾夺主。
效果是什么:孔洞里是玻璃,孔洞外是盖板
你可以把它想象成三层叠在一起:
- 底层玻璃(Blur Layer):半透明 +
backdrop-filter,负责“模糊背后的世界” - 上层盖板(Cover Layer):纯色背景,但通过 mask 挖出点阵孔洞
- 内容层(Content Layer):正常渲染的文字/按钮/导航,永远在最上面
结构上像这样:
1内容层(z=3) ← 你真正要显示/交互的内容2盖板层 + mask(z=2) ← 纯色盖板,只在孔洞位置透明3玻璃层 blur(z=1) ← 背景模糊“只在孔洞里露出来”4页面真实背景(backdrop) ← 被 blur 的对象
这种“只在孔洞里发生模糊”的感觉,天然带一点颗粒纹理与秩序感,尤其适合:
- 顶部导航/侧边栏背景(玻璃感但不抢内容)
- 需要“遮挡”又不想完全遮死的区域
- 深浅色主题都想保持一致的材质语言
一个最小使用示例(与你的 Demo 同款思路)
把 DotGlass 当作一个“覆盖层”放在容器上,就能获得左侧对比效果(白底/黑底都能用):
1"use client";2
3import { DotGlass } from "@/components/qiuye-ui/dot-glass";4
5export function DotGlassSimpleDemo() {6 const split = 50;7
8 return (9 <div className="relative h-[200px] w-full overflow-hidden rounded-xl border bg-white">10 {/* 背景内容(会被 blur 的对象) */}11 <div className="absolute inset-0 p-5">12 <div className="h-10 w-1/2 rounded-lg bg-cyan-500/35" />13 </div>14
15 {/* 覆盖层:只覆盖左侧 split% */}16 <DotGlass17 absolute18 className="left-0 inset-y-0 pointer-events-none"19 style={{ width: `${split}%` }}20 dotSize={3}21 dotGap={6}22 dotFade={0}23 blur={4}24 saturation={140}25 glassAlpha={0.45}26 coverColor="#ffffff"27 />28 </div>29 );30}
小提示:
- 容器用了
overflow-hidden,这样圆角会把盖板/孔洞一并裁切掉,边缘更干净。 coverColor通常设为“你希望孔洞外看起来是什么底色”,白底就#fff,黑底就#000。
实现思路:两层叠加 + 反向挖孔
核心不复杂,重点是几个概念的组合方式。
0) 组件结构(JSX 摘录)
DotGlass 的 JSX 结构其实就是把“三层”固定下来(玻璃 → 盖板 → 内容):
1return (2 <div className="dot-glass relative">3 {/* 1) 玻璃层:blur + 半透明背景 */}4 <div5 className="absolute inset-0"6 style={{7 background: `rgba(255, 255, 255, var(--dot-glass-alpha))`,8 backdropFilter: `saturate(var(--dot-glass-sat)) blur(var(--dot-glass-blur))`,9 WebkitBackdropFilter: `saturate(var(--dot-glass-sat)) blur(var(--dot-glass-blur))`,10 zIndex: 1,11 }}12 />13
14 {/* 2) 盖板层:纯色 + mask 挖孔(孔洞处透明) */}15 <div16 className="absolute inset-0"17 style={{18 background: "var(--dot-glass-cover-color)",19 maskImage:20 "radial-gradient(circle, transparent var(--dot-glass-dot-radius), #fff var(--dot-glass-fade-radius))",21 maskSize: "var(--dot-glass-dot-gap) var(--dot-glass-dot-gap)",22 maskRepeat: "repeat",23 WebkitMaskImage:24 "radial-gradient(circle, transparent var(--dot-glass-dot-radius), #fff var(--dot-glass-fade-radius))",25 WebkitMaskSize: "var(--dot-glass-dot-gap) var(--dot-glass-dot-gap)",26 WebkitMaskRepeat: "repeat",27 zIndex: 2,28 }}29 />30
31 {/* 3) 内容层:永远在最上面 */}32 <div className="relative" style={{ zIndex: 3 }}>33 {children}34 </div>35 </div>36);
这里有一个很实用的“组件封装思路”:把可调参数都变成 CSS 变量(如 --dot-glass-dot-gap、--dot-glass-blur),再用 React props 把值写进去。这样样式层保持纯 CSS 语义,组件层只负责“传参”和“组织层级”。
1) backdrop-filter:模糊的是“背后”,不是“自己”
很多人第一次接触会把 filter 和 backdrop-filter 混在一起:
filter: blur(...):模糊的是 元素自身(包含它的内容)backdrop-filter: blur(...):模糊的是 元素背后的像素(backdrop)
因此玻璃拟态最常见的写法是:
1.glass {2 background: rgba(255, 255, 255, 0.25);3 backdrop-filter: saturate(140%) blur(12px);4 -webkit-backdrop-filter: saturate(140%) blur(12px);5}
注意两点:
- 需要“有点背景色”:完全透明时你看不到玻璃那层“雾感”。
DotGlass用glassAlpha控制这层的透明度。 - Safari 需要前缀:
-webkit-backdrop-filter是现实世界的必需品。
2) CSS Mask:用“透明度”决定哪里能画出来
mask 这类属性,本质是在说:这个元素能不能在某个像素点“被画出来”。
一般规律可以这样记:
- mask 里 不透明(白) → 该像素“可见”
- mask 里 透明 → 该像素“不可见”
DotGlass 的关键是:给盖板层做 mask,让盖板在点阵孔洞处变透明,这样底下的玻璃层才“露出来”。
点阵孔洞用一个重复的径向渐变就够了:
1.cover {2 background: var(--cover-color);3 mask-image: radial-gradient(4 circle,5 transparent var(--dot-radius),6 #fff var(--fade-radius)7 );8 mask-size: var(--dot-gap) var(--dot-gap);9 mask-repeat: repeat;10 mask-position: center;11}
你会发现 dotFade 的意义也很直观:
dotFade = 0:孔洞边缘硬朗、清晰dotFade > 0:孔洞边缘会“柔化”一圈,视觉更温和
同样,WebkitMask* 也在 Safari 场景里很关键(Chrome/Edge 通常两者都吃)。
3) 叠层与交互:z-index + pointer-events
这个组件里我建议始终把三层固定下来(视觉稳定、心智简单):
- blur layer:
zIndex: 1(只负责材质) - cover layer:
zIndex: 2(只负责遮挡与孔洞) - content layer:
zIndex: 3(你的内容永远在最上面)
同时,把材质层都设成 pointer-events: none,避免它们抢事件。
参数怎么调:从“克制”开始
我自己常用的起点是:
dotSize: 2~3(小一点更像纹理,不像装饰)dotGap: 6~8(密度适中)dotFade: 0~1(需要更柔和就加一点)blur: 4~8(越大越“玻璃”,也越吃性能)saturation: 120~160(让背后的色彩更“通透”)glassAlpha: 0.35~0.55(孔洞里的雾感强弱)coverColor: 跟随主题(浅色#fff,深色#000)
建议只在小面积使用(比如导航条、卡片头部、侧栏局部)。模糊是昂贵的操作:区域越大、层级越多、动效越频繁,成本越高。
兼容性与性能小结(别踩坑)
- 兼容性:
backdrop-filter不是所有浏览器都一致支持,尤其是在一些“被裁剪/被 transform 的复杂叠层”里表现会更敏感。- Safari 场景务必加
-webkit-backdrop-filter与-webkit-mask-*。
- 性能:
- 尽量避免在大面积上动态改变
blur或者持续动画 blur(更推荐动画 transform/opacity)。 - 把
DotGlass做成“局部装饰层”,而不是全屏背景。
- 尽量避免在大面积上动态改变
- 视觉边缘:
- 圆角容器记得
overflow-hidden,否则孔洞/盖板会把圆角破坏得很明显。
- 圆角容器记得
组件已封装进 QiuYe UI(qiuye-ui)
DotGlass 目前已经封装在我的组件库 QiuYe UI 中,与 AnimatedButton、GradientCard、TypingText、ResponsiveTabs、ScrollableDialog 等组件属于同一套设计语言。
仓库地址:
如果你已经在项目里使用该仓库的组件组织方式,可以直接按示例从 @/components/qiuye-ui/dot-glass 引入。
结语:把“材质”当作一种信息层级
我越来越相信:好的 UI 视觉不是“加特效”,而是用更少的元素,让用户更清楚地感知层级、边界与重点。
DotGlass 的价值也在这里——它不强调自己,它只是用一个规律的点阵,把“遮挡”变得更温和,把“玻璃”变得更克制。
如果你也在做偏内容/偏氛围的界面(博客、作品集、图库、控制台的低干扰 UI),希望这个小组件能给你一点灵感。