fix(webui): 滚动边缘淡出按容器高度封顶,弹层不再淡(「渐变过猛」)

用户(2026-09-14):「你有的地方渐变用的过猛了,比如收件人候选那里」。

上一轮加的「边缘淡出」写的是上下各固定 12px —— 那是拿**长列表**的内边距
(10px)当尺子量的,可它作用在**所有** `overflow-y-auto` 上。
实测收件人候选菜单整块只有 58px 高(提示行 22px + 一条候选 34px),
上下各淡 12px 共 24px ⇒ 小半个菜单是渐变的,那条唯一的候选底部被洗白。

根因不是「12px 太大」,而是**固定像素用在了高度不固定的东西上**。所以:

1. `.overflow-y-auto` 的淡出宽度改成按可见高度封顶 `min(12px, 10%)`;
2. 弹层(`.overflow-y-auto.glass-control`)**不淡**:它是圆角+边框的独立
   表面,内容被边框截住已经「有交代」,而它高度小到淡出只剩负作用。

实测(Chromium 渲染后读像素,不是读声明的字面):
  60px 容器:新规则淡出 6px(10.0%),旧规则 12px(20.0%)
  700px 容器:新规则淡出 12px(1.7%)—— 高列表观感不变
  真实候选菜单:`getComputedStyle(...).maskImage` 从 12px 渐变变为 `none`

判据:narrow-layout 新增 4 条,钉的是**结构**而不是那个数字 ——
淡出宽度必须带上限、两个上限都必须 > 0、弹层必须豁免、两套前缀都要在。
(第一版只取了 `min(` 里的 px 就断言 > 0,把 10% 改成 0% 时照样绿,
被变异测试抓到后重写。)
This commit is contained in:
2026-09-14 15:44:08 +08:00
parent 71a3c35610
commit 00c4df4e98
3 changed files with 65 additions and 5 deletions

View File

@ -172,6 +172,47 @@ check(
compose.includes('shrink-0 px-4 md:px-6 pb-20 lg:pb-3') &&
compose.includes('sticky bottom-0')
);
// 5.9) 滚动容器的边缘淡出不能按**固定像素**给。
//
// 用户(2026-09-14):「你有的地方渐变用的过猛了,比如收件人候选那里」。
// 原来的写法是上下各固定 12px(拿长列表 10px 内边距当尺子量的),可它作用在所有
// `overflow-y-auto` 上 —— 收件人候选菜单整块才 58px(提示行 22 + 候选 34),
// 上下各淡 12px 共 24px,等于把小半个菜单洗掉。
// 根因不是 12px 太大,而是**固定像素用在了一个高度不固定的东西上**。
//
// 所以这里的判据不是「等于某个值」,而是三条**结构**约束:
// 1. 淡出宽度必须带一个上限(`min(…)`)—— 否则矮容器一定被洗掉;
// 2. 上限不能为 0(那等于没有淡出,用户上一轮的「硬截断」又回来了);
// 3. 弹层(控件档的滚动区)必须豁免 —— 它自己是圆角+边框的独立表面,
// 被边框截住已经「有交代」,而它高度小到淡出只剩负作用。
const maskRule = (css.match(/\.overflow-y-auto\s*\{[\s\S]*?\n\}/) || [''])[0];
check(
'滚动边缘淡出的宽度有上限(不是写死的固定像素)',
/min\(\s*\d+px\s*,/.test(maskRule),
'淡出宽度写成了固定 px —— 矮容器(候选菜单只有 58px)会被洗掉大半'
);
/*
* 上限本身也要验。第一版只取了 `min(` 里**第一个**数字(12px)就断言 > 0 ——
* 把 10% 改成 0% 时它照样绿(变异 B 抓到的):两个上限都生效才是真的收敛。
*/
const cap = maskRule.match(/min\(\s*(\d+)px\s*,\s*(\d+)%\s*\)/);
const [capPx, capPct] = cap ? [Number(cap[1]), Number(cap[2])] : [0, 0];
check(
'上限是「像素与百分比取小」(高列表不变、矮容器收敛),且两个上限都 > 0',
capPx > 0 && capPct > 0,
`上限取到了 px=${capPx} / %=${capPct} —— 0 等于没收敛(矮容器仍被洗)或没淡出(硬截断回来了)`
);
check(
'弹层(控件档滚动区)不做边缘淡出',
/\.overflow-y-auto\.glass-control\s*\{[\s\S]*?mask-image:\s*none/.test(css),
'候选菜单这类弹层还在淡 —— 它高度只有几十像素,首尾项会被洗白'
);
check(
'淡出规则同时带 -webkit- 前缀(Electron 里以哪个为准不由我们定)',
(maskRule.match(/-webkit-mask-image:/g) || []).length === 1 &&
(maskRule.match(/[^-]mask-image:/g) || []).length === 1
);
check('根视口使用 100dvh 跟随软键盘', css.includes('@supports (height: 100dvh)'));
// 6) 横向内边距在窄屏收窄(px-6 在 375px 屏上白吃 48px)