sticky 毛玻璃头部内容穿透:WebKit 的两个耦合陷阱
在某个 Tauri 桌面应用里,侧栏有一个固定在顶部的「分组标题栏」:背景用了半透明深色 + backdrop-filter: blur(22px),期望滚动时呈现毛玻璃效果把列表内容模糊挡住。在 Chrome 开发者工具里预览一切正常,但打包进 WebView(macOS WKWebView)一跑,效果立刻破防——列表文字清晰地穿透头部显示出来,甚至还有一条细带出现在头部上方。
现象
- 向下滚动列表时,列表项文字透过固定头部清晰可读,
backdrop-filter的模糊效果像完全没有生效。 - 滚动到一定位置后,顶部出现一条薄带,里面居然也显示着列表内容——头部上方本不该有任何内容。
- 静止(未滚动)状态下完全看不出问题,截图验证也只截了静态态,导致这个 bug 在开发阶段一直漏掉。
根因
这是两个独立 bug 叠加造成的,拆开看才清楚:
1. WebKit 的 backdrop-filter 不对滚动兄弟元素采样
在 Chromium 里,position: sticky 元素的 backdrop-filter 能采样在它下方滚动的兄弟内容,产生毛玻璃效果。但在 WebKit / WKWebView 里,这个采样只针对元素「背后」的层(stacking context),对同一 scroll container 内滚动的兄弟内容无效——blur 根本就没运算那块内容。
结果:背景全靠 rgba(16,24,36,0.78) 的不透明度遮挡,而 alpha=0.78 意味着约 22% 的列表颜色直接透出来,深色背景下文字依然清晰可辨。
2. scroll 容器 padding-top 导致 sticky 定位偏离顶部
sticky top:0 的锚点是滚动视口(scrollport)的 content-box 顶边,而 scrollport 的 content-box 从 padding-top 之后才开始计算。如果容器有 padding-top: 14px,那 sticky 元素实际固定在距容器顶部 14px 处——上方那 14px 的 padding 区域属于滚动范围,列表内容滚过去时会出现在头部上面,形成那条「幽灵条带」。
- WebKit 里
backdrop-filter对同容器内的滚动内容无效,半透明背景挡不住文字。 - scroll 容器有
padding-top时,sticky top:0并不固定在容器最顶,上方留有一条内容可以「越过」的缝隙。 - 这两个 bug 仅在滚动状态下可见,静止截图永远是好的,极易在开发阶段被漏过。
正解
修复一:把头部背景改为近不透明
不依赖 backdrop-filter 遮挡内容,直接把 alpha 拉到 0.97~0.99(或完全不透明):
/* 之前 */
.head {
background: rgba(16, 24, 36, 0.78);
backdrop-filter: blur(22px);
}
/* 之后 */
.head {
background: rgba(16, 24, 36, 0.98); /* alpha ≥ 0.97 */
backdrop-filter: blur(22px); /* 保留,作为边缘光晕的装饰,无害 */
}注意:如果项目用了设计 token(如 --glass-bg-strong),不要直接改 token,因为同一个 token 可能被浮层、Modal 等元素复用——那些元素位于页面根层,backdrop-filter 是有效的,改掉会破坏它们。只在 sticky 头部局部覆盖即可。
修复二:去掉 scroll 容器的 padding-top
把原本容器顶部的间距挪到头部元素自身的 padding 里:
/* 之前 */
.scroll {
overflow-y: auto;
padding: 14px 12px; /* 顶部 14px 成为 sticky 的死角 */
}
.scroll .head {
position: sticky;
top: 0;
margin: -14px -12px 18px; /* 负 margin 抵消容器 padding */
}
/* 之后 */
.scroll {
overflow-y: auto;
padding: 0 12px 14px; /* 顶部 padding 清零 */
}
.scroll .head {
position: sticky;
top: 0;
margin: 0 -12px 18px; /* 去掉顶部负 margin,不需要抵消了 */
padding-top: 14px; /* 原来的顶部呼吸感挪到这里 */
}sticky 头部的视觉遮挡靠高 alpha 背景色,不靠 backdrop-filter;滚动容器不留 padding-top,把间距转移到头部自身 padding 里,让 sticky top:0 真正固定在顶部边缘。
排查提示
验证时必须在滚动状态下截图,而非静止状态——这个 bug 在非滚动状态下完全隐形。在 Tauri 开发模式下,如果修改 CSS 后热更新没有生效,先确认不是单实例守卫把旧进程保留导致的(Vite 热更新是生效的,但如果旧 WebView 进程还在,看到的仍是旧样式)。
一句话外卖
在 WebKit 里,
backdrop-filter遮不住同容器内的滚动内容;而 scroll 容器的padding-top会让sticky top:0留出一条幽灵缝隙——两者都要靠结构修改解决,不是调参数能绕过去的。