材质自动推断与 120Hz 运动降级
指针速度感知、窗口移动与缩放、帧预算探针与省电信号,0ms 降级与 140ms 非对称恢复的抗掉帧架构。
材质自动推断与 120Hz 运动降级
折射与背景模糊都靠 backdrop-filter 采样身后的内容。材质随元素一起移动时,取样会落后于元素的位置,滤镜层拖着一块错位的背景走;在 120Hz(ProMotion)屏幕上拖动窗口时,重滤镜还会挤占本就只有 8.3ms 的帧预算。TxBaseSurface 因此在运动期间降级,停下后再恢复。
为什么需要运行时自动推断?
客户端的运行环境变化很大:
- 用户可能突然按住标题栏,飞速拖动窗口。
- 笔记本可能在电量低时进入省电模式。
- 老旧核显可能跑不动重度滤镜,连续掉帧。
如果让业务开发者在各个场景里手写 if (isDragging) { ... },代码会迅速腐化,也极易遗漏。TuffEx 把这套判断下沉到 TxBaseSurface 的 autoDetect 环境感知(默认关闭)。
宿主也可以不开它,自己设置 moving 属性。TuffEx 里,TxBaseAnchor 的弹出面板在部分打开、关闭动画期间设置它(surfaceMotionAdaptation 默认 auto),TxFlipOverlay 在翻转动画期间设置它,TxCard 在惯性跟随(inertial)运动期间设置它。
autoDetect 环境感知信号矩阵
打开 autoDetect 后,组件综合以下运行期信号。除第 3 条外都是页面级的:所有打开 autoDetect 的材质共用一套监听,监听、电池订阅与帧探针只在至少有一个这样的材质挂载时存在。
- 指针速度感知:只看按下状态的指针。相邻两次采样至少间隔 16ms,速度取 ,一次采样超过 即判定为快速移动。没按下的悬停移动不算;按住不动或放慢后,140ms 内没有新的快速采样,信号结束。
- 窗口移动与缩放:页面自己的
resize事件自动计入。网页收不到窗口移动的事件,要由宿主转发:在 Electron 里把BrowserWindow的move(以及will-move)转给渲染进程,调用notifySurfaceWindowMotion()。最后一次报告后 140ms,信号结束。 - 自身与祖先的 transform:在材质元素及其所有祖先上监听
transform/translate的transitionstart、transitionend、transitioncancel,只认发生在这些元素自身的过渡,从别处冒泡上来的不算;MutationObserver观察它们的内联style,任一元素带内联transform即视为运动,全部去掉后视为停止。 - 帧预算探针:挂载后趁空闲采样约 30 帧(最多 3 秒),以最快的一成帧间隔估计这块屏幕的帧预算(不超过 16.67ms,120Hz 屏上约 8.3ms)。三分之一以上的帧超过 1.5 个预算记为
reduced;中位帧超过 2.5 个预算记为minimal;中位帧超过 250ms 说明页面正被浏览器节流,这次读数作废。降级后依次隔 30、60、120、300 秒重测,恢复后 1.5 秒再确认一次;页面隐藏或失去焦点时不测。 - 省电信号:通过 Battery Status API,未充电且电量不高于 20% 视为省电。宿主可以用
setSurfacePowerSaving(true | false)覆盖(例如接 Electron 的powerMonitor),传null交还给电池接口。
前三条是运动信号,决定何时降级;后两条决定渲染哪种材质:省电或 reduced 时,refraction、glass、acrylic 换成 blur;minimal 时,所有采样背景的材质(含 blur)换成纯色 flat。mica、plain、pure、mask 不受影响。
getSurfaceEnvironment() 以只读 ref 给出页面当前的 pointerMoving、windowMoving、powerSaving 与 frameTier,阈值以 SURFACE_POINTER_SPEED_THRESHOLD、SURFACE_MOTION_SETTLE_MS、SURFACE_LOW_BATTERY_LEVEL 导出。
0ms 降级与 140ms 非对称恢复
为了消除运动中的卡顿,TuffEx 采用非对称时钟:
- 降级(0ms):任一运动信号出现,当帧降级,不做防抖。重滤镜立刻让出 GPU,拖动跟手。
- 恢复(140ms 迟滞):信号消失后不立即切回,防止手势在阈值附近轻微颤抖时反复切换;等 140ms 内没有新信号,才开始恢复。
这个时钟以 useSurfaceMotionGuard(emitFallback, { settleMs }) 导出:onMotionStart() 立即回调 true,onMotionEnd() 在安静满 settleMs(默认 140ms)后回调 false。知道自己何时运动的宿主,可以用它驱动 moving。
降级快,恢复分层
下文的“运动结束”,对 autoDetect 而言是 140ms 迟滞之后;对 moving 而言是宿主把它置回 false 的时刻。
blur、glass 与 acrylic 整体换材质:
- 降级:运动一开始,渲染模式立即换成
fallbackMode。默认是mask,色膜不透明度取fallbackMaskOpacity(TxBaseSurface默认 0.95,TxCard传 0.26);也可以设为pure。 - 恢复:运动结束后先保持降级
max(transitionDuration, settleDelay),默认max(299, 150)= 299ms,再换回原材质。
refraction 不换材质,而是分层降级:
- 降级:玻璃层和滤镜层在 140ms 内淡出,一层半透明遮盖在 180ms 内升到 0.55,折射参数的混合进度降到 0.28,折射描边立即隐藏。
- 恢复:运动一结束就开始,没有等待期。参数在 182ms 内回满;遮盖先停 60ms,再用 227ms 撤完,到 287ms 结束;玻璃层和滤镜层延迟 80ms 后用 220ms 淡回;恢复结束后再过 60ms,描边用 220ms 淡回。恢复途中再次运动,会直接回到降级状态。
pure、mask、flat、mica、plain 没有采样背景的滤镜层,运动时不做任何处理。
状态机转换图
mode 本身就是 blur、glass 或 acrylic 的材质,静止时保持原材质,运动时同样走 Fallback_Mode 与 Settling 这一圈。运动中的材质因此不会拖着错位的背景走,停下后又回到完整的折射。