---
title: Alpha 噪点掩膜与光斑追踪
description: "解决 Overlay 混合导致底色发灰的难题，通过 mask-composite: intersect 实现晶莹微质感与光标反光追踪；TxCard 的折射光源还能用弹簧跟随指针。"
---

# Alpha 噪点掩膜与光斑追踪

在拟真材质设计中，噪点（Grain）与高光（Specular）是赋予界面物理触感的关键细节，但错误的实现会破坏色彩纯净度。

## 传统 Overlay 叠加噪点发灰的根源

许多设计师习惯在半透明卡片上叠加一层 5% 灰度噪点，并使用 CSS `mix-blend-mode: overlay`。
在浅色或淡色调（Alpha 约 16%）的色洗（Wash）上，叠加混合模式的算法公式：
$$\text{Overlay}(A, B) = \begin{cases} 2AB & B < 0.5 \\ 1 - 2(1-A)(1-B) & B \ge 0.5 \end{cases}$$
会导致每个像素点的亮度被强制向灰色拉扯，在 Retina 视网膜高清屏上呈现出一层**肮脏的灰色麻点**，彻底丧失通透感。

## mask-composite: intersect 解决方案

TuffEx 的核心突破在于：**不修改像素的 RGB 颜色，而是将分形噪点编织到透明通道（Alpha Mask）中**：

:::TuffCodeBlock{lang="scss"}
---
code: |
  @supports (mask-composite: intersect) {
    .tx-status-hint__wash {
      background-color: var(--tx-status-hint-accent, #67c23a);
      // 线性渐变与分形噪点进行交集合成，仅控制透光程度
      mask-image:
        linear-gradient(
          to right,
          rgb(0 0 0 / var(--tx-status-hint-wash-strength, 0.26)),
          rgb(0 0 0 / calc(var(--tx-status-hint-wash-strength, 0.26) * 0.45)) 38%,
          transparent
        ),
        $grain; // 140px 见方的 feTurbulence 分形噪点 SVG
      mask-size: 100% 100%, 140px 140px;
      mask-repeat: no-repeat, repeat;
      mask-composite: intersect;
    }
  }
---
:::

这种做法让噪点只决定“色彩以多大的物理密度透出”，底层背景的纯净度丝毫不受污染。噪点的 alpha 平均约 0.6，所以前缘 0.26 的强度在浅色主题里约合 16%；暗色主题取 0.2，因为同样的色调在深色页面上更显眼。

:::TuffDemoWrapper{demo="ConceptNoiseMaskDemo"}
:::

## 卡片物理反光追踪（Specular Light）

在 `TxCard` 上，用户滑动鼠标时，卡片表面与边缘会呈现随光标游走的微弱径向高光（Specular Light）。它由 `specular` 属性控制：不设置时跟随 `clickable`，可点击的卡片默认就有，禁用的卡片不画。

- **相对坐标采样**：指针在卡片上移动时，组件把光标位置换算成卡片内的百分比坐标，写成反光层的 `--tx-card-light-x`、`--tx-card-light-y`。
- **极低透明度漫射**：高光是一道 600px 的圆形径向渐变，中心 6% 的白色，到 40% 处完全透明。强度严格压制在 `0.06`，只在暗色主题下绘制（浅色底上 6% 的白看不出来），高对比度模式下不画，绝不夺取卡片内部文字的主角地位。指针进入时用 240ms 淡入，离开时淡出。

## 阻尼平滑与法线贴图错觉

为了防止鼠标快速移动时光斑出现机械跳动，反光点不直接跳到光标处，而是每帧向它靠近一段：
$$\vec{P}_{\text{render}}(t) = \vec{P}_{\text{render}}(t-1) + \alpha' \cdot \left(\vec{P}_{\text{cursor}}(t) - \vec{P}_{\text{render}}(t-1)\right), \qquad \alpha' = 1 - (1 - \alpha)^{\Delta}$$
α = 0.18 是 60Hz 下一帧走完的比例，Δ 是帧间隔除以 16.67ms（限制在 0.1–2）。按 Δ 复合以后，120Hz、144Hz 屏幕上的走法与 60Hz 一致；按 60Hz 推算，一次跳变 16 帧（约 267ms）走完 95%。指针刚进入卡片时，反光点直接出现在光标下，而不是从上次离开的地方滑过来；系统开启“减少动态效果”时，反光点始终直接跳到光标处，也不做淡入淡出。

高光与卡片边缘 1px 细线相互干涉：光斑掠过边框时，边框占据的那一圈另画一道 240px、中心 30% 白色的更紧更亮的光，用 `mask-composite: exclude` 挖掉内容区，只留在边框上，形成如同光线擦过倒角边缘的法线高光错觉（Normal Map Illusion），大大增强了三维立体触感。这一圈只画在 `solid` 卡片的实线边框上：`dashed` 的虚线会把间隙一起照亮，所以不画；`plain` 没有边框。

:::TuffDemoWrapper{demo="ConceptCardSpecularDemo"}
:::

## 折射光源跟随

另一种跟随指针的光属于折射材质本身。`TxCard` 的折射光源跟随默认关闭，只在 `background="refraction"` 且打开 `refraction-light-follow-mouse` 时工作：

- **相对坐标采样**：指针在卡片上移动时，组件把光标位置换算成卡片内 0–1 的归一化坐标，作为光源的目标点；指针离开卡片，目标点回到中心 (0.5, 0.5)。
- **移动折射的光源点**：光源点经 `TxBaseSurface` 的 `refraction-light-x/y` 写成 `--tx-surface-refraction-light-x/y`，决定折射滤镜层与色膜层上几处径向光晕的位置，其中一处落在对角的对称点上。这条路径不另加白色高光层，白色高光是上面的 `specular`。
- **随指针微调强度与角度**：光源离中心越远，折射强度最多加 16 × `refraction-light-follow-intensity`（默认 0.45，即最多 +7.2）；折射角度按同一系数向指针方向插值。

## 弹簧平滑

为了防止鼠标快速移动时光斑机械跳动，光源点不直接跳到目标点，而是由一根逐帧积分的弹簧带过去：
$$v \leftarrow \left(v + k\,(P_{\text{target}} - P)\,\Delta\right) c^{\Delta}, \qquad P \leftarrow \operatorname{clamp}(P + v\,\Delta,\ 0,\ 1)$$
k 是 `refraction-light-spring-stiffness`（默认 0.15，取值限制在 0.01–0.55），c 是 `refraction-light-spring-damping`（默认 0.6，限制在 0.55–0.99），Δ 是帧间隔除以 16.67ms（限制在 0.1–2，第一帧取 1）。位置差与速度都小于 0.002 时视为停稳，循环随之停止。关掉 `refraction-light-spring`，光源点直接跳到目标点。按 60Hz 推算，默认参数下一次跳变只冲过目标约 3%，约 130ms 后稳定在 5% 以内。位置这一步也乘 Δ，所以 120Hz、144Hz 屏幕上光源点的走法与 60Hz 基本一致。

:::TuffDemoWrapper{demo="ConceptSpecularLightDemo"}
:::
