跨端: 回复球 → 回复条 加共享元素转场(WebUI 的 FLIP「按钮长成面板」)
用户 2026-09-21:「我记得 webui 行为是按钮变成对应的写邮件页面或输入框吧,你做的啥?」
## WebUI 的真实行为(读它的实现,不是凭印象)
`MailView.tsx:990-1010` + `ComposePage.tsx:57-79` 做的是 **FLIP**
(First-Last-Invert-Play):
const from = 球的 getBoundingClientRect(); // ① 起点
const to = 面板的 getBoundingClientRect(); // ② 终点
const scale = Math.max(0.04, from.width / to.width);
const dx = from.left + from.width/2 - (to.left + to.width/2);
const dy = from.top + from.height/2 - (to.top + to.height/2);
el.animate([
{ transform:`translate(${dx}px,${dy}px) scale(${scale})`,
opacity: 0.3, borderRadius: '28px' }, // ← 起点圆角 = 球的半径
{ transform:'none', opacity:1, borderRadius:'0px' }
], { duration: 220, easing: 'cubic-bezier(0.22, 0.61, 0.36, 1)' });
观感就是**球从自己的位置和半径"长"成一整幅面板**。用户说的
「按钮变成输入框」正是这一条 —— 而且它有**两处**:回复球→回复条、
以及写信 FAB→写信页。
★ 它还显式尊重无障碍:`prefers-reduced-motion` 时直接 return(不打动画)。
## 鸿蒙的对应能力:`geometryTransition`
官方文档(`ts-transition-animation-geometrytransition`)原文:
· 「通过安排绑定的 in/out 组件的 frame、position,将视觉焦点由旧视图位置
引导到新视图位置」—— 就是 FLIP 的系统实现;
· 「**必须配合 `animateTo` 使用**才有动画效果,动效时长、曲线跟随
`animateTo` 中的配置,**不支持 `animation` 动画**」;
· 「同一个 id 只能有**两个**组件绑定,且分别作为 in(新视图)和
out(旧视图)两种不同类型角色,**不能多个组件绑定同一个 id**」。
⇒ 三条约束逐条满足:
· 两端绑同一个 id `reply-morph`(且**只有**这两处,判据可查);
· 状态切换写在 `animateTo` 的**闭包内**(不是 `.animation()`);
· 用默认 `follow: false`(两端是 `if/else` 互斥出现,不是"始终在树上")。
## 时长/曲线的取值(不是我拍的)
**220ms + `cubic-bezier(0.22, 0.61, 0.36, 1)`** —— 逐字取 WebUI 那行 FLIP 的参数。
那个贝塞尔恰好就是本仓 `Theme.easeRise` 的定义(`easeRise` 当初就是从这行抄来的),
所以代码里直接引它,没有新写一条曲线。
新增令牌 `Theme.durMorph = 220`,**没有**复用 `durRise`(200):两者是不同动效
(元素出入场 vs 共享元素续接),WebUI 那边也是两个值(150 / 220)——
合成一个的话以后想单独调其中一个就得先拆开,拆的时候必漏调用点。
## 设备验证(做了什么 / 没做成什么)
✓ 点球之后**回复条确实展开**(`dumpLayout` 命中「回复给 pi@root.realtest」
与「输入回复内容」两处)——功能链路通。
✗ **没能在设备上看到中间帧**:`snapshot_display` 一次往返 ~1.5-3s,
而这条动画 220ms ⇒ 探针比被测对象慢一个数量级。连拍 4 张
y=1600 的白区跨度全是 `(30,1007)`(即每张都已是终态)。
★ 这正是此前"连测七八轮没有中间帧、并编出三个错误理论"的同一个坑
(那次的结论记在本仓:**探针对被测变化不敏感时,量的是噪声**)。
⇒ 这一条的**动画本体**只能由用户在真机上看,我不声称已验。
顺带把「取消」键改成反向 morph(`closeReplyWithMorph()`)——
WebUI `MailView.tsx:945` 专门记过这个不对称:「打开有 morph、收起是瞬间消失 —— 不对称」。