跨端: 鸿蒙动画补齐 —— 并且发现原来的「逐字一致」是假的(令牌存在 ≠ 动画用了它)
用户:「A,同时把鸿蒙app的动画补齐」。 ## 先纠一条错的前提(这是本轮最有价值的发现) `Theme.ets` 的注释与 `harmony-nav` 的判据**都**写着:「三个数与 WebUI **逐字一致**: `--ease-out-soft: cubic-bezier(0.22,1,0.36,1)`、`--dur-fast: 120ms`、`--dur-base: 180ms`」, `paneRiseIn()` 就按 `durBase(180)` + `easeOutSoft` 做。 三个令牌**确实存在**(`index.css:310-312`)—— 但这句话把「令牌存在」当成了「动画用了它」: | WebUI 里 | 真实用途 | |---|---| | `--dur-base: 180ms` | **只**用在壁纸淡入(`:747`) | | `--ease-out-soft (0.22,1,0.36,1)` | **只**用在 transition(壁纸、控件变色) | | **所有 @keyframes 动画** | 硬编码 **150ms / 200ms** + `cubic-bezier(0.22, 0.61, 0.36, 1)` | 全仓 `(0.22,0.61,0.36,1)` 出现 **5 次**(rise-in×3 / cal-in×2),`(0.22,1,0.36,1)` 只出现 **1 次**(令牌定义处)。**是两根不同的曲线** —— 旧代码的面板入场比 WebUI 慢 30ms 且曲线偏软。 所以令牌拆成两组,各对齐各的:控件类 `durFast + easeOutSoft`;动画类 `durRise(150)/durMenu(140)/durCal(200) + easeRise(0.22,0.61,0.36,1)`。 ## 补的动画(对齐 WebUI 三个 @keyframes) - `menuIn()` —— 弹层:下移 4vp + 缩到 0.985 + 淡入(`@keyframes menu-in`)。 ★ 适用范围照搬 WebUI 注释那条窄口径(「只给真正是弹层的东西」):那条规则曾挂着 `glass-control`,于是每次切视图**所有按钮与输入框一起淡入位移**,09-15 被摘掉。 挂到写邮件页的账号候选列表上。 - `calendarSlide(forward)` —— 日历翻月:从 ±12% 横向滑入(`cal-in-next/prev`)。 方向由新增 `@State slideForward` 带进 `animateTo` 闭包;网格键加 `monthKey()` 前缀 保证月份一变所有键全变(否则"跨年同名月"那类边角会静默不播)。 - 写信页整页 `rise-in`、回复框 `rise-in`(WebUI 挂在回复框本身,不是外层遮罩 —— 挂遮罩上会让整个屏幕一起位移,看起来是"页面在动"而不是"框弹出来")。 **实测确认真的会播**(不是"编译过了"):按本仓记录的手法把 `durCal` 临时改成 8000 做慢动作,连拍三帧 —— 截图硬证**两张月历同时在屏**(九月淡出、十月从右侧 12% 滑入), 验完还原成 200ms。 ## 判据:从「令牌存在」改成「动画真的用了那个令牌」 `harmony-nav` 那条判据原文锚在令牌上,所以它对上面那个 bug **一辈子全绿**。 重写为: - 从 WebUI `index.css` **读出** `rise-in` 的真实时长与曲线(`150ms` + 那条 bezier), 再断言鸿蒙的 `durRise` 与曲线字面量与之逐字一致 —— 而不是"仓库里有没有 180 这个数"; - ★ 把 `paneRiseIn()` 的**函数体抠出来**单独断言它引的是 `durRise/easeRise`, 且**不得出现** `durBase/easeOutSoft`。 **这一步不能省 —— 我第一版就漏了它**:断言了令牌存在、也断言了曲线字面量存在, 但没断言函数用了它们;于是把函数体换回旧的错值后判据**仍然全绿**。 变异自检抓住了这一点,补上函数体断言后同一个变异 ⇒ 判红 ✓。 与今天修的另一处同源:**判据要锚在"这个东西被用在哪",不是"它存在"**。
This commit is contained in:
@ -267,29 +267,62 @@ export class Theme {
|
||||
static readonly fontBody: number = 14;
|
||||
|
||||
/*
|
||||
* ── 动画令牌(对照 WebUI `index.css` 的 `--dur-*` / `--ease-out-soft`)──
|
||||
* ── 动画令牌(对照 WebUI `index.css`)──
|
||||
*
|
||||
* 用户(2026-09-17):「一方面一点动画都没有」。
|
||||
* 实测确认:改造前全仓 `animateTo` / `transition` / `animation` **一次都没用过** ——
|
||||
* 所有页面切换、详情推入、面板展开都是硬切。
|
||||
*
|
||||
* 三个数**与 WebUI 逐字一致**(`index.css:280-282`):
|
||||
* --ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1)
|
||||
* --dur-fast: 120ms (控件变色:hover / active)
|
||||
* --dur-base: 180ms (面板与内容入场)
|
||||
* 这样两端的“快/慢”手感是同一个尺子,而不是各自拍一个数。
|
||||
* ★★ 2026-09-18 更正:这里原先写着「三个数与 WebUI **逐字一致**」,**那句是错的**。
|
||||
* 它把「令牌存在」当成了「动画用了那个令牌」。实测(`client/electron/src/index.css`):
|
||||
*
|
||||
* `--dur-base: 180ms` 只用在**壁纸淡入**(:747 `transition: opacity …`)
|
||||
* `--ease-out-soft (0.22,1,0.36,1)` 只用在 **transition**(壁纸、控件变色 :1169)
|
||||
* 而**所有 @keyframes 动画**用的是 150ms + `cubic-bezier(0.22, 0.61, 0.36, 1)`
|
||||
*
|
||||
* 全仓 `(0.22,0.61,0.36,1)` 出现 **5 次**(rise-in ×3 / cal-in ×2),
|
||||
* `(0.22,1,0.36,1)` 只出现 **1 次**(就是令牌定义处)。**这是两根不同的曲线。**
|
||||
* 旧代码把面板入场按 `(0.22,1,0.36,1)` + 180ms 做 ⇒ 比 WebUI **慢 30ms 且曲线偏软**,
|
||||
* 而当时的判据(`harmony-nav` "缓动必须是 0.22,1,0.36,1")锚在**令牌**上,
|
||||
* 所以这个错它一辈子抓不到。
|
||||
*
|
||||
* 所以下面分**两组**,各自对齐各自该对齐的东西:
|
||||
* 控件类(transition) —— `durFast 120` + `easeOutSoft`
|
||||
* 动画类(@keyframes)—— 各自时长 + `easeRise`
|
||||
*/
|
||||
/** 控件变色(hover / active)—— WebUI `--dur-fast: 120ms` */
|
||||
static readonly durFast: number = 120;
|
||||
/**
|
||||
* 壁纸淡入 —— WebUI `--dur-base: 180ms`。
|
||||
*
|
||||
* ★ **只用于壁纸/整体淡入,不是面板入场**(面板入场是 `durRise`)。
|
||||
* 这个区分是 2026-09-18 查出来的,之前两者被当成同一个数。
|
||||
*/
|
||||
static readonly durBase: number = 180;
|
||||
/** 面板/内容入场 —— WebUI `@keyframes rise-in` 实测 **150ms** */
|
||||
static readonly durRise: number = 150;
|
||||
/** 弹层入场 —— WebUI `.animate-menu-in` 实测 **140ms** */
|
||||
static readonly durMenu: number = 140;
|
||||
/** 日历翻月 —— WebUI `.cal-slide-next/prev` 实测 **200ms** */
|
||||
static readonly durCal: number = 200;
|
||||
|
||||
/**
|
||||
* 缓动曲线:`cubic-bezier(0.22, 1, 0.36, 1)` —— 与 WebUI `--ease-out-soft` 同一根曲线。
|
||||
* **transition** 用的缓动:`cubic-bezier(0.22, 1, 0.36, 1)` = WebUI `--ease-out-soft`。
|
||||
*
|
||||
* 用 `curves.cubicBezierCurve` 而不是 `Curve.EaseOut` 之类的枚举:
|
||||
* 枚举是系统预设的**另一根**曲线,观感与 WebUI 对不上(而“对齐 WebUI”正是本项要求)。
|
||||
* 枚举是系统预设的**另一根**曲线,观感与 WebUI 对不上。
|
||||
*/
|
||||
static readonly easeOutSoft: ICurve = curves.cubicBezierCurve(0.22, 1, 0.36, 1);
|
||||
|
||||
/**
|
||||
* **@keyframes 动画**用的缓动:`cubic-bezier(0.22, 0.61, 0.36, 1)`。
|
||||
*
|
||||
* ★ 与 `easeOutSoft` **不是同一根曲线**(前者第二段控制点是 0.61,后者是 1)。
|
||||
* 两者别互换:WebUI 里 transition 用 `--ease-out-soft`,而所有动画
|
||||
* (rise-in / cal-in)硬编码用的是这一根。
|
||||
*/
|
||||
static readonly easeRise: ICurve = curves.cubicBezierCurve(0.22, 0.61, 0.36, 1);
|
||||
|
||||
/**
|
||||
* 窗格入场时的上浮量(vp)。
|
||||
*
|
||||
@ -307,24 +340,60 @@ export class Theme {
|
||||
* (`combine`/`animation` 会就地改自身),多处共用同一个实例会互相干扰;
|
||||
* 每处调用各拿一个新的。
|
||||
*
|
||||
* ★ 两边时长**不对称**:出场(120ms)比入场(180ms)快。
|
||||
* 同长会让新旧两层半透明地叠着,看起来像"闪一下"——
|
||||
* 用户对"闪"敏感(09-14 否掉过整屏淡入)。
|
||||
* ★ 时长/曲线用 `durRise` + `easeRise`(**150ms + 0.22,0.61,0.36,1**)——
|
||||
* 即 WebUI `rise-in` 的**真实取值**。改之前用的是 `durBase(180)` + `easeOutSoft`,
|
||||
* 那是令牌值而不是动画值(见上面令牌区的更正说明)。
|
||||
*
|
||||
* # 实测如何验证它真的生效
|
||||
*
|
||||
* `snapshot_display` 有 ~1s 往返,180ms 的过渡**抓不到**(连拍三帧全一样,
|
||||
* 会得出"动画没做"的错误结论)。做法:临时把 `durBase`/`durFast` 改成 20000,
|
||||
* 再过 6s 截图 —— 正常应看到新旧两层**同时半透明**地叠着
|
||||
* (实测已确认:收件箱与日历交叉淡入,样例点 (628,1450) 是混合值 164,165,167)。
|
||||
* 验完把时长还原成 120/180。
|
||||
* ★ 两边**不对称**:出场用 `durFast`(120ms)比入场快。
|
||||
* WebUI 那边 `rise-in` 是对称的(一个 @keyframes 两用)—— 因为它是**逐个元素**
|
||||
* 挂载即播,新旧两棵子树不会同时可见。而 ArkUI 这里是 `if/else` 换子树,
|
||||
* 两层会**同时半透明地叠着**,同长看起来就是“闪一下”(实测 6 秒取样确认),
|
||||
* 所以出场必须更快地让位。这是两端机制不同带来的**必要差异**,不是随手拍数。
|
||||
*/
|
||||
static paneRiseIn(): TransitionEffect {
|
||||
return TransitionEffect.asymmetric(
|
||||
TransitionEffect.opacity(0).combine(
|
||||
TransitionEffect.translate({ y: Theme.riseInOffset })
|
||||
).animation({ duration: Theme.durBase, curve: Theme.easeOutSoft }),
|
||||
TransitionEffect.opacity(0).animation({ duration: Theme.durFast, curve: Theme.easeOutSoft })
|
||||
).animation({ duration: Theme.durRise, curve: Theme.easeRise }),
|
||||
TransitionEffect.opacity(0).animation({ duration: Theme.durFast, curve: Theme.easeRise })
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹层入场:下移 4vp + 缩到 0.985 + 淡入(对齐 WebUI `@keyframes menu-in`)。
|
||||
*
|
||||
* WebUI `.animate-menu-in` 的注释把适用范围钉得很窄,这里照搬同一个口径:
|
||||
* 「只给**真正是弹层**的东西(候选/下拉菜单自己穿)」——
|
||||
* 它原本还挂着 `html.view-switch .glass-control`,于是**每次切视图页面上所有
|
||||
* 按钮与输入框一起淡入位移**(几十个元素同时动),2026-09-15 被摘掉。
|
||||
* 所以本方法**只给弹层**(账号选择器、下拉候选),不要挂到常驻控件上。
|
||||
*
|
||||
* 入场方向是**从上往下**(`translateY(-4px)` → 0):弹层从触发点的下方展开,
|
||||
* 从上方“落”下来。与 `rise-in` 的**上浮**方向相反,别混。
|
||||
*/
|
||||
static menuIn(): TransitionEffect {
|
||||
return TransitionEffect.opacity(0).combine(
|
||||
TransitionEffect.translate({ y: -4 })
|
||||
).combine(
|
||||
TransitionEffect.scale({ x: 0.985, y: 0.985 })
|
||||
).animation({ duration: Theme.durMenu, curve: Theme.easeRise });
|
||||
}
|
||||
|
||||
/**
|
||||
* 日历翻月的横向滑入(对齐 WebUI `@keyframes cal-in-next/prev`)。
|
||||
*
|
||||
* `forward = true` → 下一月,从**右侧**滑入(`translateX(12%)`);
|
||||
* `forward = false` → 上一月,从**左侧**滑入(`translateX(-12%)`)。
|
||||
*
|
||||
* 百分比在 ArkUI 的 `translate` 里是**相对元素自身尺寸**的 —— 与 CSS 语义一致,
|
||||
* 所以 12% 就是“滑入自身宽度的 12%”,不是拍一个 vp 数(那样在大屏上会变小)。
|
||||
*/
|
||||
static calendarSlide(forward: boolean): TransitionEffect {
|
||||
return TransitionEffect.asymmetric(
|
||||
TransitionEffect.opacity(0).combine(
|
||||
TransitionEffect.translate({ x: forward ? '12%' : '-12%' })
|
||||
).animation({ duration: Theme.durCal, curve: Theme.easeRise }),
|
||||
TransitionEffect.opacity(0).animation({ duration: Theme.durFast, curve: Theme.easeRise })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user