Files
Jyotisha/docs/tasks/TASK-site-motion-20261022.md
T

12 KiB
Raw Blame History

TASK · 全站动效:换页、页签、盘型、列表、弹窗、抽屉、提示、数字(2026-10-22)

2026-10-23 更新:本单不再单独执行。 产品推翻了下面的 D3「不引入动画库」,改为一律用 Motion(见 TASK-ui-kit-migration-20261023.md 的 D2)。本单的动效规范表仍然有效,由迁移单 K0 做成 Motion 预设;M0~M4 并入迁移单 K1~K5。issue #98、#100、#101 不再执行。

设计原型(可在「现在 / 改后」之间对比):https://claude.ai/artifact/7u9goivnmTNSGTNWNeVMQd 。原型和本任务书不一致时,以本任务书为准。

基线

  • 写本单时的 origin/staging 是 8743f6151。每个 issue 开工时,以当时的 origin/staging 为准。
  • 本单不动表、不改 workflow,所以交给 agent 循环执行,分三个 issue;拆分见文末「循环交接」。
  • 本单排在 TASK-onboarding-journey-20261022.md 的 issue 一至三之后,排在它的 issue 四(启程动效)之前。启程动效直接复用本单 M1 的动效底座。
  • BUG 编号:开工时用 grep -oE "BUG-[0-9]+" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1 核对当前最大号,从下一个号开始。

现状实证

  1. frontend/DESIGN.md §6「Motion & Interaction」现行规定:
    • 只有一条缓动曲线 --ease-out。
    • 时长分档:Micro 120 ms、Standard 180 ms、Sheet 280 ms。
    • 「Spatial: Instant」:侧栏状态变化和手机侧栏都是瞬间切换,没有过渡。
  2. 各处切换现在都是瞬间完成:
    • 侧栏导航(components/app-sidebar.tsx)换页、当前项高亮。
    • 星盘页签与盘型(components/chart-page/chart-page-view.tsx、chart-type-picker.tsx):盘型一换,整盘重画。
    • 星历换日期(components/ephemeris/ephemeris-page.tsx)。
    • 档案增删(components/people/people-page.tsx)。
    • 会话收藏、删除(components/sidebar-session-row.tsx)。
    • 报告中英切换(components/personal-report/report-actions.tsx)。
  3. 设置弹窗(components/account-dialog-overlay.tsx)只有淡入;导出抽屉(components/personal-report/report-export-drawer.tsx)和顶部提示(components/ui/sonner.tsx)用的是各自的默认过渡,没有统一。
  4. 依赖里没有动画库。gsap 只在 components/chat-message-row.tsx 里用。

决策记录(产品负责人 2026-10-22 拍板)

# 决定
D1 全站动效规范按下表重写,推翻 DESIGN.md §6 的 180 ms 上限和「Spatial: Instant」。新用户启程里那几处更长的动效(飞勾 700 ms、合盘靠拢、七项全满的星点)仍只在启程流程里用
D2 回弹曲线只用于「成功」和「提示」:顶部提示、勾、一次性提示。换页、换盘、弹窗、抽屉一律用 --ease-out
D3 不引入动画库。页面切换用浏览器原生的页面过渡能力(View Transitions),其余用 CSS 和 Web Animations API;不支持的浏览器直接切换,不报错。先做半天验证(M0)再铺开

新规范(写进 DESIGN.md §6,替换原表):

动效 用在哪 时长 曲线
按下反馈 按钮、行、卡片 120 ms ease-out
页面切换 侧栏换页。旧页淡出,新页按侧栏顺序从下方或上方滑入 12–18 px;标题同步换 进 280 ms / 出 150 ms --ease-out
选中块跟随 侧栏当前项、分段控件 260 ms --ease-out
页签切换 星盘四页签、设置三分区。下划线滑动,内容按左右方向滑入 260 ms --ease-out
盘型切换 星体从旧宫位移动到新宫位,上升星座淡入淡出 440 ms --ease-out
换人 头像翻面,盘格线重画,星体就位 300 ms + 800 ms --ease-out
翻日期 日期上下滚动,内容左右翻页,方向跟随前后 260 ms --ease-out
发送消息 用户气泡从输入框升起 260 ms --ease-out
切换会话 对话内容交叉淡入,不滑动 200 ms --ease-out
列表增删与重排 新项展开出现,删除项收起,其余平滑补位 260–280 ms --ease-out
弹窗 从触发它的按钮处放大出现,关闭时缩回去 220 / 160 ms --ease-out
抽屉 导出、手机侧栏从边缘滑入,遮罩同时淡入 280 / 200 ms --ease-out
顶部提示 从顶部落下 320 ms 回弹 cubic-bezier(.34,1.56,.64,1)
数字变化 点数余额上下滚动:扣点往下,充值往上 360 ms --ease-out
中英切换 段落依次淡出、换文字、再淡入,间隔 40 ms 约 320 ms --ease-out
展开收起 大运小运、思考步骤(沿用现状) 240 ms --ease-out

硬红线

  1. 不新增任何动画或过渡依赖。gsap 不扩散到 chat-message-row.tsx 以外的地方。
  2. next build 后 / 仍为 ○ Static。首屏 gzip 变化在 ±2% 以内;动效代码按需加载,不进首屏。
  3. 遵守 prefers-reduced-motion:位移和缩放全部去掉,只保留 ≤150 ms 的淡入淡出;数字直接变化。
  4. 动效不得挡住操作:
    • 进行中可以继续点击,新动作直接打断旧动效。
    • 不得为了等动效播完而禁用按钮或延迟路由。
  5. 动效只回应用户操作,不得用来填补等待。「一次等待一次揭幕」不变:揭幕后没有骨架、转圈、「正在加载」(星盘页盘位骨架仍按 BUG-1016 例外处理)。页面切换时目标页的数据没到,照现有规则留白,不得播一段动效来拖时间。
  6. 只对 transform、opacity、颜色、clip-path 做动效,不触发布局;列表补位用 FLIP(先记录旧位置,改完再从旧位置滑过去)。
  7. 遵守冻结规则:Home() 的 useState / useRef 数不增长。动效逻辑放在 lib/motion/ 和各组件里。
  8. 流式回答的逐字释放、思考步骤展开等现有行为不改。
  9. 改 UI 的同一提交里同步更新 frontend/DESIGN.md。

任务分解

M0 页面过渡验证(半天,issue 一的第一步)

分别试两种接法,在进度记录里写清结论:

  • A:Next 16 的 experimental.viewTransition 加 React <ViewTransition>。
  • B:在侧栏导航处自己用 document.startViewTransition 包住路由跳转,等新页提交后再结束过渡。

每种接法在下列环境里各走一遍「对话 → 星盘 → 星历 → 档案 → 报告」:

  • 桌面 Chrome
  • 桌面 Safari
  • iOS Safari(Grok 侧做不了就写成环境缺口,留给真机清单)
  • Android Chrome

每次记录:能否用、首屏 gzip 变化、有无闪烁。

选型规则:选能用、改动小的那种。两种都不稳定时,退回方案 C:只在主内容区做交叉淡入,不用 View Transitions。三种结果都要写进进度记录。

验收:进度记录里有一张「方案 × 浏览器」表,并写明最终选了哪种。

M1 动效底座(issue 一)

  • globals.css 里加动效令牌:上表各档时长,以及 --ease-out 和 --ease-spring。
  • lib/motion/ 里放以下工具,都要能被打断,并且尊重减少动态效果:
    • prefersReducedMotion()
    • animate():对 Web Animations API 的薄封装
    • flipList():列表补位
    • rollNumber():数字滚动
    • swapContent():内容交叉换场
  • DESIGN.md §6 按上表重写,注明 D1 / D2 / D3 和启程流程的例外。

验收

  • lib/motion 有单测:减少动态效果时不产生位移;连续调用两次时,第二次会打断第一次。
  • 首屏 gzip ±2%。

M2 第一批:对话与页面框架(issue 一)

  • 页面切换:按 M0 选定的方案实现。方向按侧栏顺序:点到下面的页,新页从下方进;点到上面的页,从上方进。标题同步换。
  • 侧栏当前项的底色块滑到新位置。
  • 手机侧栏从左滑入、遮罩淡入(推翻「Spatial: Instant」)。
  • 发送消息时,用户气泡从输入框位置升起。点追问胶囊时同理,从胶囊位置升起。
  • 切换会话时,对话内容交叉淡入。
  • 点数余额在侧栏和聊天顶栏同步滚动。
  • 设置弹窗、确认框从触发按钮处放大出现,关闭时缩回去。
  • 顶部提示从顶部落下,用回弹曲线。

验收

  • 每项动效至少有一个组件测试或交互测试:确认动效被调用,且减少动态效果时不被调用。
  • 快速连点侧栏 5 次:最终停在最后点的那一页,且没有残留的旧页。
  • tsc / lint / npm test / build 都过。

M3 第二批:星盘页(issue 二)

  • 四个页签:下划线滑动,内容按左右方向滑入。
  • 盘型切换:同一颗星在新旧盘型里都存在时,从旧位置移动到新位置;新盘里才有的星淡入,消失的星淡出。上升标签交叉淡入。北印度盘和西洋轮盘都要做。
  • 换人:头像翻面,盘格线重画(stroke 描线),星体就位。
  • 大运展开小运沿用现有的展开收起,时长统一为 240 ms。

验收

  • 盘型切换后,每颗星的最终位置与切换前一致的直接渲染逐像素相同(快照测试或坐标断言)。动效不得改变数据。
  • 连续快速切换 D1 → D9 → D10,最终停在 D10 的正确位置。

M4 第三批:其余页面(issue 三)

  • 星历前一天 / 后一天:日期上下滚动,内容按方向左右翻页;点「今天」时按相对方向翻。
  • 档案加人、删人,会话收藏置顶、删除:都用 FLIP 补位。删除动效放完之后才从列表里移除,但数据请求不等动效。
  • 报告中英切换:段落依次交叉淡入。继续保持 BUG-1126 的 key 修复,切换后不得残留旧语言的节点。
  • 导出抽屉(report-export-drawer.tsx)从边缘滑入;手机上从底部滑入。

验收

  • BUG-1126 的回归测试仍然通过。
  • 删除会话、删除人物的接口失败时,行会回到原位,并给出现有的错误提示。

让步顺序

做不完时按以下顺序先砍:

  1. 中英切换的逐段错开(改成整段一起淡入淡出)
  2. 换人时的格线重画
  3. 星历日期的上下滚动(只保留内容翻页)
  4. 数字滚动

页面切换、侧栏选中块、弹窗、抽屉、顶部提示、盘型切换不让。

循环交接

  • 执行方:Grok(Windows + Docker)。每个 issue 一个分支,名字写在下表;只推自己的分支。
  • Grok 自跑:tsc --noEmit、npm run lint、相关测试文件、npm run build(记录 / 是否 Static 和首屏 gzip)。
  • Claude 验收时跑:前端全量 npm test,逐条对比基线;首屏 gzip 用同一种量法复核;DESIGN.md 是否同步更新。
  • 真机清单:docs/testing/site-motion-20261022.md,由 issue 一创建,后两个 issue 往里追加。清单逐条写「点哪里、应该看到什么」,交给产品负责人走。
  • 每个 issue 在同一变更里更新 docs/BUG_HISTORY.md(体验改进类记 enhancement)、CHANGELOG.md 和 frontend/DESIGN.md。
issue 内容 分支 前置
全站动效(一) M0 + M1 + M2 codex/site-motion-1-20261022 新用户启程(三)合入
全站动效(二) M3 codex/site-motion-2-20261022 全站动效(一)合入
全站动效(三) M4 codex/site-motion-3-20261022 全站动效(二)合入

开工前置命令

git fetch origin --prune
git worktree add -b <上表分支> .worktrees/<上表分支去掉 codex/> origin/staging
cd .worktrees/<…> && git status -sb
grep -oE "BUG-[0-9]+" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1
cd frontend && npm ci && ./node_modules/.bin/tsc --noEmit && npm run lint && npm run build   # 记下基线 gzip