切换外观
回应托盘 · 动画记录
状态:2026-10-28,第四轮候选 08 回应托盘按第三轮的六步流程(reference → data → story → motion → direct → ship)做成完整形式(forms/trays.html,手机 390 × 844)。第 1–4 节(参考、数据、故事、手势与交互)在任何东西动起来之前写下,代码照它做;第 5–6 节是实现、与故事的出入和验证。原型探索,不是规范。起点是第四轮探索时的静态草图(只在本机),候选与灵感见 INSPIRATION.md 的 08 与 #78、#75;阅读加作答的范本是 D 手机首页(home-motion.md:答一件少一件、回执跟着条目走、长按代替悬停)与腕上一瞥(watch-motion.md:换轮三拍、自动讲一遍);两层法则与交互标准见 CONCEPTS.md。数字都由 scenario.mjs 回放现算,规则见 2.1;推算脚本只留在本机,不入库。
回应托盘与 D 有一处根本不同,故事要正面回答:D 把所有问题排成一样的卡片,按“多急”排;托盘也按多急排,但让每个托盘的高度说“要你做几下”。它仍是真正作答的地方:托盘里按下去就是回应,不加“去回答 →”。
1 参考(reference)
1.1 两张参考图读到了什么
#78 Family 钱包的托盘(a-family-trays-frames.png,12 秒加油流程的 12 帧)。底下的设置列表压暗到约四成、一动不动;白色圆角托盘(半径约 30 px,四周离屏幕边约一指宽)浮在上面,贴着屏幕下沿。每一步只有托盘的顶边上下移动:选链四行勾选,顶边在屏幕中部;选金额六个块,矮一截;自定金额带数字键盘,顶边升到状态栏下面,最高;核对页列三条 $12 与合计,又落回中部;确认页是一个大数字 $36。底部的大按钮一直在同一个位置,文字从 Continue 形变到 Confirm。作者写明高度“故意各不相同,让进展一目了然”。
#75 Liquid Glass 的两段演示(a-liquidglass-tabbar-frames.png、a-liquidglass-fluidtime-frames.png)。音乐 App 一向下滚,约 0.4 秒内两层并成一行:四个标签只剩当前那一个,“正在播放”胶囊降进同一行。锁屏上通知一条、两条叠上来,“9:41”的字高从约四成屏高压到约两成,字宽几乎不变。玻璃的折射与高光是拟物渲染。
1.2 搬什么,不搬什么
| 要素 | 参考 | 托盘 |
|---|---|---|
| 高度就是这一步要你做多少 | Family:每一步托盘高度不同 | 搬,并且只让高度说这一件事:托盘高 = 头部 + 剩下几下 × 一排。点一下就完的只有一排按钮,最矮;推后、定时刻要先点开再挑,多一排;按掉一下矮一排,答完收成一行回执 |
| 托盘贴底、顶边移动 | Family:一个托盘贴在屏幕下沿 | 不搬成一个贴底的托盘:五个托盘叠成一列,读的是从上到下,所以上沿不动(上沿是阅读的锚),变的是下沿,下面的托盘一次上移补位 |
| 按钮文字形变,共有的部分不动 | Family:Continue → Confirm,共有的“Con”不动 | 搬成“按下的那个选项的字留下,成为回执的一部分”:回执写“主水管 · 你选了「整根挖开换」”,题目那一行不动 |
| 背景压暗、托盘浮起 | Family:列表压到四成 | 只在长按与展开时搬:其余降到七成(ix 的标准);平时不压暗,每个托盘都能直接按 |
| 两层并成一行 | Liquid Glass 标签栏:0.4 秒并成一行 | 搬:答完收成一行回执;两下的托盘点了第一下,第一排并进标题(成为标题旁的“推到…”小签),第二排升上来 |
| 数字给新来的让出高度 | Liquid Glass 锁屏:通知越多,9:41 越矮 | 不搬:英雄数字的位置与字号永不变(D 的规则,也是第一眼的锚);数的值已经是“剩下多少”,再让字形变矮就是两种编码说同一件事 |
| 玻璃、折射、高光、手机壳 | Liquid Glass、两张图里的机身 | 不搬。平面、纸白墨黑,托盘只有一道细框,不画机身 |
1.3 从 D 直接沿用的
D 已经把“作答工具怎样动”做完了一遍,托盘沿用它的部件与纪律(home-motion.md 4.3、4.4),不另造一套:
- 同一件事的托盘、回执、“已收到”那一行、详情是同一个盒子变形;盒子用上盖、中段、下盖三块拼,只动
transform(d-motion.mjs 的makeBox、morph、reveal)。 - 只在切点读一次布局,之后只写;揭开用纸色盖板
scaleY,不动clip-path。 - 回执按问题 id 认、跟着条目走;先“已保存”,下一轮整理回放后“已收到”(respond.mjs 的
createResponseStore、responseStatus)。 - 手指保护:作答后 1.12 秒内,补位滑到手指下面的按钮不接连点。
- 长按 0.45 秒、移动 10 px 取消;详情从托盘长出、下拉跟手收回、左缘右滑交给 Safari 当“收回”。
1.4 Safari iOS 的约束
照 home-motion.md 1.4:约 60 Hz,只动 transform 与 opacity;阴影不做动画(托盘本来就没有阴影);不用 backdrop-filter;没有滚动锚定,补位时自己补 scrollTop;左缘右滑是后退、页面顶部下拉是 Safari 的回弹,首页都不绑;长按要关掉选字与放大镜(-webkit-touch-callout: none、user-select: none)。托盘页比 D 简单的一处:它是表单形式页,滚动发生在页内的一个滚动区里,地址栏伸缩不推动它。
2 数据(data)
2.1 规则
| 量 | 怎么算 | 出处 |
|---|---|---|
| 开着的问题 | 根层与子层条目里 currentAsk(s, id) 不为空的(属于条目的当前版本);本页答过的不算“开着” | variants/shared.mjs currentAsk,与 D 的 compose() 相同 |
| 托盘的顺序 | D 的 urgencyKey:档位 → 24 小时内到期 → 期限 → 份额 → 排名 | variants/d.mjs |
| 期限 | ask.due_at,没有则取条目 due_at(done 除外);“还有 N 小时”按本帧时刻算 | 同 D |
| 一个问题几下 | 托盘上最短的那条路要点几下,按下表;按 normalizeAsk(ask) 的 expects 与 actions 现算 | respond.mjs normalizeAsk |
| 英雄数字“N 下答完 M 个问题” | N = 开着的问题各自还剩几下之和(两下的托盘点了第一下,就只剩一下);M = 开着的问题个数 | 本页 |
| 托盘高度 | 头部一排 + 剩下几下 × 一排;回执一行 | 本页 |
| 本页回执 | 按条目 id 与 updated_at 存,按问题 id 认:同一问题随改写重登记(ask id 不变),仍是你答过的 | respond.mjs createResponseStore;D 的 answeredRecord |
| 让位 | 回执在下一轮整理回放之后(responseStatus 为 acknowledged)离开托盘列,落到托盘下面的“已收到”一栏;问题关上而本页没答过:这一轮里有反馈指向它,写“你在原处……”(responseSentence),没有就写“随改写失效”,只在关上的那一轮列出 | forms/sketches/stage.mjs responseSentence;D 的 lapsedAsk |
几下的口径(写在页脚,详情里“这一问要几下”一节逐件写明):
| 回应 | 托盘里的控件 | 最少几下 | 托盘几排 |
|---|---|---|---|
| confirm、approve、choose | 一排按钮,按一个就答完(approve 的“评论”、choose 的“回一句”是一句话的次要入口,不计) | 1 | 1 |
| defer | 先点“推到…”,再挑一天(明早、周一、周三,或自己挑日子) | 2 | 2 |
| schedule(时刻) | 先点“选时间”,再挑一个时刻(建议的时刻,没有就是两小时后、明早 09:00,或自定义) | 2 | 2 |
| reply、schedule(一句话),有建议句 | 一排建议句,点一句就发出 | 1 | 1 |
| reply(一句话),没有建议句 | 先点“回一句”,写好再点发送;另要打字 | 2 + 写一句 | 2 |
| reply(金额),有预填或建议金额 | 一排金额,点一个就交 | 1 | 1 |
| reply(照片) | “拍一张”;另要拍照 | 1 + 拍照 | 1 |
2.2 composite 的 f26:6 下答完 5 个问题
08-16 00:00,旁白「车道下的铜管裂了」(frames[26].title)。与 D 的默认帧、份额拨盘第 3 拍是同一帧。
托盘 · 问题(ask.short) | expects · 按钮 | 几下 | 档位 · 份额 · 期限 | |
|---|---|---|---|---|
| 1 | 主水管 driveway-wet · ask-23「主水管:挖开换还是免挖?」 | choose:整根挖开换 / 免挖穿新管 | 1 | 关键 · 24 份 · 8/17 10:00 前,还有 34 小时;本轮新登记 |
| 2 | FTPS 计时 issue-13587 · ask-24「#13596 计时修复:批准?」 | approve:批准(推荐)/ 要求修改;评论是次要入口 | 1 | 常规 · 6 份 · 无期限;本轮新登记 |
| 3 | 待审 PR pr-reviews-0814 · ask-22「三个旧 PR 推到哪天?」 | defer:推到… | 2 | 常规 · 6 份 · 无期限;f23 首次登记,f26 随改写重登记 |
| 4 | 开学文件 signe-school-start · ask-21「Signe 的同意书签了吗?」 | confirm:已签 | 1 | 微量 · 1 份 · 8/18 23:59 前 |
| 5 | Ture 发烧 ture-pickup-0812 · ask-19「VAB 报了吗?」 | confirm:已申报 | 1 | 微量 · 1 份 · 无期限 |
合计 1 + 1 + 2 + 1 + 1 = 6 下答完 5 个问题,4 个点一下就完,“三个旧 PR 推到哪天?”要先点“推到…”再挑日子。f27 这五个问题原样重登记(ask id 不变),f28 加到 7 个、8 下;真实数据里到两周结束这五个问题都没有答(f28 旁白「两周结束:水管还没回」)。
2.3 两周里的分量
| composite | story | |
|---|---|---|
| 有开着问题的轮次 | 28(f00 是空状态) | 36(cold-00 是空状态) |
| 英雄数字 | 4–9 下;最多 9 下 8 个(f08、f15);f23–f25 是最少的 4 下 3 个 | 1–6 下,每一轮“几下”都等于“几个” |
| 有两下托盘的轮次 | 18 轮(f01、f02、f08、f09、f12–f17、f19、f20、f23–f28) | 0 |
| 不同的问题按分量 | 一下 20 个(confirm 5、choose 9、approve 6);两下 6 个(defer 2、时刻 schedule 1、没有建议句的 reply 3) | 一下 39 个(choose 23、confirm 11、approve 1、带建议句或预填的 reply 与 schedule 3、照片 1);两下 0 |
story 是手写情境,作者给每个要一句话、一个数的问题都配了建议句或预填金额(“定本月 API 上限”预填 5000,建议 5000 / 5200 / 6400),于是两周里没有一个问题要点两下:托盘一样高。这是数据本身的样子,页面照实画,不为它另造分量。
2.4 讲一遍的样张
| composite | story | |
|---|---|---|
| 叠起 | f25 → f26:3 个托盘 4 下 → 5 个托盘 6 下;主水管、#13596 两个新问题进来,开学文件与 Ture 发烧换位(Ture 发烧从低档降到微量) | f19 → f20:4 下 4 个 → 5 下 5 个;“周三演示前,开放范围怎么定?”随改写失效、离开,“定本月 API 上限”(一排金额)与“财务建议 5000 元”(批准)进来 |
| 按下 | 替你按“#13596 计时修复:批准?”的“批准”(6 → 5),再按“三个旧 PR 推到哪天?”的“推到…”(5 → 4)与“周一”(4 → 3)。真实数据里没有这两步,是演示,讲完撤回,回到 6 | 替你按“今晚拍一张修后的墙角”的“拍一张”(5 → 4):真实数据里 20:36 你回了照片(f21 的反馈 ask-24 · photo),按下后与你自己按的一样,可以撤回 |
| 让位 | (composite 的五个问题之后都没答,不演) | f20 → f21:回执先填实写“已收到”,再落到“已收到”一栏;托盘列剩 4 个,数字仍是 4 |
3 故事(story)
论点。 一个问题要你做几下,托盘就有几排:点一下就完的最矮,要先点开再挑的高一排。按掉一下,托盘矮一排;答完,它原地收成一行回执,大数字减去那几下;下一轮整理读到了,回执才让出位置。高度、右栏的刻度和大数字说的是同一个数,永远对得上。
英雄数字:“N 下答完 M 个问题”的 N。 f26 是 6。它回答“答完这些要我花多少力气”,是这一页唯一的大字(等宽 68 px,位置与字号永不变),下面一行把它拆开:“4 个点一下就完;‘三个旧 PR 推到哪天?’要两下:先点‘推到…’,再挑一天。”四个形式的英雄数字连成一条:脉冲是这一轮收到多少(f20 的 234),拨盘是一共能放多少(64),D 是要你答几件(5),托盘是答完要几下(6)。
第一眼。 竖屏上方一个大“6”,下面五个圆角托盘按档位与期限叠成一列;四个一样矮,第三个高一排;每个托盘右边一栏写“1 下”“2 下”,栏里每一下一道灰刻度,和那一排按钮对齐;朱红小方块只标登记了的问题。
3.1 三拍
| 拍 | 时刻 | 发生什么 | 观众此刻 |
|---|---|---|---|
| 1 叠起 | 一轮整理落地,约 1.0 秒 | 切点换字;回执先填实;0.40 秒数字翻到新值;离开的托盘上收淡出,留下的一次移到新位置,新问题的托盘在自己的位置揭开,它们的高度已经是几下;0.96 秒新问题外框描一次朱红 | 有 6 下,高的那个要两下 |
| 2 按下 | 每一下即刻,0.4–0.5 秒 | 按一个就完的:托盘原地收成一行回执(上沿不动,下沿上收),右栏的刻度跟着收掉,数字减一,下面的一次上移补位。两下的第一下:第一排并进标题,第二排升上来、由虚变实,托盘矮一排,数字减一;第二下同上收成回执 | 每一下都看得见少了一排 |
| 3 让位 | 下一轮整理落地,约 1.0 秒 | 0–0.30 回执的空心勾填实、写“已收到”;0.40–0.88 回执一次下移,越过托盘,落进托盘下面“已收到”一栏、变成一行纸面上的字;托盘列补上空位;关上但不是你在这里答的,同样下沉,写“你在原处……”或“随改写失效” | 答过的去了哪,还剩几下 |
3.2 第 1 拍:叠起(换轮)
沿用 D 的换轮节拍表(d-motion.mjs 的 BEATS.turn),只换成托盘自己的元素:
| 时刻 | 什么动 | 怎么动 |
|---|---|---|
| 0 | 切点 | 页头的日期与轮次、托盘里的期限字样、“已收到”一栏的计数一帧硬切 |
| 0–0.30 | 回执 | 读到的回执空心勾填实,小字换成“已收到 · Agent 在 12:00 的整理中读到” |
| 0.40–0.66 | 英雄数字 | 差 1 滚一格,差 2 以上一帧硬翻(D 的规则);下面一行同刻淡入新的拆法 |
| 0.40–0.64 | 离场 | 关上的问题若没有落点(条目已撤下)上收淡出 |
| 0.40–0.88 | 主角 | 回执与关上的托盘一次下沉到“已收到”一栏,每个晚 0.06 秒,越过别的托盘时在上层;后半段盒子从托盘变成一行纸面 |
| 0.48–0.80 | 移位 | 留下的托盘一次移到新位置;几下变了的(问题改写成另一种回应)盒子同时长高或收矮 |
| 0.60–0.96 | 进场 | 新问题的托盘从上沿往下揭开,错开 40 ms |
| 0.96–2.56 | 新问题 | 新登记的托盘外框描一次朱红 |
3.3 第 2 拍:按下(本页作答)
按住时按钮缩到 0.97(0.12 秒);松手是切点。
| 时刻 | 元素 | 从 → 到 |
|---|---|---|
| 0–0.12 | 托盘里的按钮、期限一行、右栏刻度 | 淡出。题目那一行不动,是这个盒子的锚 |
| 0–0.20 | 托盘的盒子 | 上沿不动,下沿收到一行回执的高度 |
| 0.10–0.30 | 回执一行 | 淡入:“主水管 · 你选了「整根挖开换」· 已保存”;空心勾 0.12–0.30 描出;“撤回”在右端,焦点到它 |
| 0.20–0.46 | 英雄数字 | 减一,滚一格;拆法一行同刻换 |
| 0.20–0.52 | 下面的托盘 | 一次上移补位,不过冲 |
| 到 1.12 | 手指保护 | 补位滑到上一次触点下面的按钮暂不接点按 |
两下的第一下(“推到…”“选时间”“回一句”):0–0.12 第一排的按钮淡出,它的字移到标题右边成一枚小签“推到… ×”;0–0.24 第二排(日期一排,原来是虚线框、不能按)升到第一排的位置、虚线换成实线,盒子下沿收一排;0.12 数字减一;0.12–0.40 下面的托盘上移一排。点小签上的“×”或按 Esc 退回第一步:逆序,0.30 秒,数字加一。第二下照“按下”走。
撤回逆序、更快,0.40 秒:回执展开回托盘(两下的托盘回到两排),数字加上那几下,下面的托盘下移。
3.4 第 3 拍:让位(下一轮整理落地)
时间表就是 3.2 的“回执”与“主角”两行。两种变体:
- 同一问题随改写重登记(composite f26 → f27,五个都是):回执照样填实、下沉到“已收到”,问题仍按你答过的算,不回到托盘列,数字不反弹(D 4.4 的规则)。
- 问题改写成另一个问题(ask id 变了):托盘以新问题回到列里,题目下多一行“你 00:00 答过上一版:……”,你答的那一下不算进新问题。
3.5 讲一遍
只在顶层直接打开、地址不带 ?frame=、没有 referrer 时演一次;任意键或轻点跳过,按下的键只用来跳过,跳过时直接到讲一遍的终态;页底“↺ 重演”在顶层打开时都有(重演先撤掉讲一遍替你按下的)。画廊、合页 all.html(followFrames)、体验站弹框与带帧地址都不演;prefers-reduced-motion 时也不演。
| 秒 | composite(约 7.8 秒) | story(约 6.4 秒) |
|---|---|---|
| 0 | 直接落在 f25(不演过渡),页脚写“讲一遍 · 任意键或轻点跳过” | 落在 f19 |
| 0.9 | 第 1 拍:换到 f26(4 → 6) | 第 1 拍:换到 f20(4 → 5) |
| 2.8 | 第 2 拍:替你按“批准”(6 → 5) | 第 2 拍:替你按“拍一张”(5 → 4),真实数据里的回答 |
| 4.4 | 再按“推到…”(5 → 4) | 第 3 拍:换到 f21,回执填实、下沉到“已收到” |
| 5.2 | 再按“周一”(4 → 3) | |
| 6.8 | 尾声:替你按的两下是演示,一起撤回(3 → 6) | |
| 终态 | f26,6 下答完 5 个问题,什么都没替你答 | f21,“拍一张”的回执在“已收到”里,可以撤回 |
composite 讲的是“一下和两下”,story 讲的是“按下和让位”:story 两周没有两下的问题(2.3),composite 在 f26 之后没有真实的回答(2.2)。两份数据各取自己有的,不编。
3.6 两层与约束
- 信息层(约 98%)。 英雄数字与拆法、托盘里的题目、期限、按钮、回执、“已收到”一栏、右栏的“N 下”。墨黑、灰阶加一个朱红。
- 概念层(约 2%)。 右栏里的灰刻度:一下一道,竖向对齐那一排按钮;与托盘的高度、英雄数字共用同一个数(每一道刻度就是数字里的 1、托盘里的一排)。它只随托盘动:按掉一下就收掉一道,换轮时跟着托盘移。静止时零运动,不飞点,不出数字与朱红。
- 颜色。 墨黑:题目、按钮、数字;推荐的选项是实心墨色。灰:刻度、细框、期限与小字。朱红:只给登记了的问题,题目前的小方块,与新登记那一轮的外框描一次;回执和“已收到”里没有朱红。
- 绝不动。 英雄数字的位置与字号;托盘的排序规则(动画只表现
urgencyKey排出的结果);题目一行在作答时的位置。 - 每个元素一次只走一段。 一拍里每个元素从旧位置到新位置只有一段运动,不折返。
- 不提前。 下一轮整理落地之前,回执不变实、不离开托盘列。
- 预算。 只动
transform与opacity,切点读一次布局;一拍里同时动的元素不超过列表本身(composite 最多 8 个托盘)。
4 手势与交互
手机没有悬停:轻点直接起作用,长按代替悬停(照 home-motion.md 3.5)。
| 手势 | 在哪 | 对应 ix 标准 | 动效 | 时长 |
|---|---|---|---|---|
| 轻点按钮 | 托盘、详情底部 | 作答(托盘专属) | 托盘原地收成一行回执,数字减一,下面一次补位 | 0.52 秒;手指保护到 1.12 秒 |
| 轻点“推到…”“选时间”“回一句” | 两下的托盘 | 作答的第一下 | 第一排并进标题,第二排升上来,矮一排,数字减一 | 0.40 秒 |
| 轻点小签上的“×” | 点过第一下的托盘 | 第一下的逆 | 逆序 | 0.30 秒 |
| 轻点“撤回” | 回执、“已收到”的行 | 作答的逆 | 逆序、更快 | 0.40 秒 |
| 轻点托盘(按钮以外) | 托盘、回执、“已收到”的行 | 点击 → 展开详情 | 外扩 8 px → 盒子长到整屏 → 内容淡入 | 0.48 秒;收回 0.40 秒 |
| 长按 0.45 秒 | 托盘、回执、“已收到”的行、英雄数字 | 悬停 → 概要卡 | 外扩 8 px,其余降到七成,概要卡贴下沿(放不下贴上沿) | 0.15 秒;收 0.12 秒 |
| 左滑过 80 px | “已收到”的行 | 知道了(托盘专属) | 跟手;松手从当前位置收走,计数减一;不过线 0.20 秒弹回 | 跟手 |
| 下拉(内容在顶) | 详情 | Esc → 收回 | 盒子跟手缩回托盘,过四分之一屏或够快就收回,否则弹回 | 跟手;剩余按比例 |
| 左缘右滑 | 详情 | Esc → 收回 | Safari 画返回,本页直接到终态 | 系统 |
| 下拉、右滑 | 托盘列 | 不绑定 | 只有 Safari 的回弹 | — |
| Tab / Enter、空格 / Esc | 托盘与行 | 悬停 / 展开 / 收回 | 同上;收回后焦点回到托盘 | — |
| ← / → / 空格 | 页面 | 换轮 / 连播 | 第 1 拍;连播每 5.6 秒一轮 | — |
| 任意键、轻点 | 讲一遍途中 | 跳过 | 直接到讲一遍的终态 | — |
细则:
- 长按的概要卡写
itemFacts的同一组字段(来自哪条流、几份、本轮怎么变、本轮进了几条、开着的问题与期限),再加一行“最少 N 下:……”;子层的事项没有根层份额,只写标题与所在的上一层。长按英雄数字出“6 下 = 主水管 1 + FTPS 计时 1 + 待审 PR 2 + 开学文件 1 + Ture 发烧 1”。按住 0.15 秒起这一件底色微压暗;移动超过 10 px 或开始滚动取消;松手后同一次触摸补发的点按吞掉;卡出着时点它或这一件,从这一件长出详情,点别处收起。 - 展开详情:盒子从托盘的位置长到整屏(圆角 18 → 0),题目作为锚移到顶部;详情写为什么在这里(档位、份额、期限、问题开了多久)、“这一问要几下”(这一问的路径与全页口径)、份额 · 逐轮与本轮进了它(
ix.mjs的两节,只给根层事项)、依据,底部是与托盘同样的作答控件。收回:内容淡出,盒子缩回原位,外扩收回。非嵌入页打开详情时pushState,左缘右滑就是收回。 - 点外:详情或概要卡开着时,点托盘列以外(桌面上是窄栏以外)收回。再点:概要卡出着时再点同一件,从它长出详情;详情里点“‹”收回。
- 连播:长按、Tab 概要与详情停住连播,收起 0.4 秒后接着播(ix 的
resume);作答停住连播且不自动接着播,免得刚按的被下一轮卷走。换轮、←/→、合页顶栏选帧立即收起概要、详情与点了一半的第一下。 - 键盘:Tab 到托盘出概要(与长按同义);Enter 或空格打开详情(托盘上的空格归它,不是连播);Esc 依次收回第一下、概要、详情。
- 脚本接口
window.foldTrays,照foldHome:model、go(i)、hover / unhover / open / close / ixAt / paused / pointOf / rowOf / ixState,answer(name, actionId)、step(name)(两下的第一下)、undo(name)、waiting()(开着的问题个数)、taps()(英雄数字)、facts(name)(这一件在哪、什么样:mark为 tray / stepped / receipt / ledger / prior)、top()、keys(),讲一遍的tell()、skip()、telling()。
5 实现与出入(motion → direct)
5.1 怎样做的
- 文件。 trays.html、trays.mjs、trays.css,用形式页框(shell.mjs,帧导航、概念卡、核对数据);在手机宽度(≤ 640 px)下页框收成一条“⌂ ‹ 时刻与旁白 ›”,概念卡与帧刻度隐藏,托盘列就是整屏;桌面上是居中的 390 宽一列,只有一道细框。
- 复用,不另造。 动画部件全部来自 d-motion.mjs(
morph、makeBox、reveal、roll、ghostOf与 D 的时间表BEATS.turn / answer / undo / local、GROW、SHRINK、PRESS);回应与回执来自 respond.mjs(normalizeAsk、createResponseStore、responseStatus、bindResponses、nextMorning);概要卡与详情里的“份额 · 逐轮”“本轮进了它”来自 ix.mjs;“你在原处……”来自sketches/stage.mjs的responseSentence。本页只新写了:几下的口径(effortOf)、托盘与回执的标记、两下托盘的第一下(step、unstep两拍,0.40 / 0.30 秒)和讲一遍。 - 布局即数据。 托盘的一排固定 40 px 加 6 px 间距,头部固定,所以托盘高 = 头部 + 剩下几下 × 46 px(两份数据每一轮都核过,6.2)。右栏刻度是每一排的
::after,跟着那一排出现、消失、移动,不另有状态。 - 一个切点读一次布局。 每一拍先量(
measureAll),再按键补丁(data-key的 keyed patch),再量一次,然后只建transform/opacity动画;回执的勾填实、新问题外框是不透明度;托盘长高与收矮是三块拼的盒子在动,元素本身不改高度。 - 讲一遍按 3.5 的时间表用定时器推进,用的就是页面自己的
go、give、stepTray;composite 替你按下的几条记录在讲完时一起撤回,story 按下的是真实数据里的回答,留下、可撤回;跳过时直接落到终态(composite 回到 f26 且什么都没替你答,story 到 f21 且回执在“已收到与关上”里)。
5.2 与故事的出入
- 推后的日子。 3.5 写的是“明早 / 周一 / 周三”。f26 是 8/16 00:00,下一个 09:00 还在同一天,写“明早”是错的;日子按与此刻的关系命名:同一天的早上叫“今早”,第二天叫“明早”,“周一”若与明早是同一天就顺延成“下周一”(f27 就是这样)。
- 定时刻的选项。 2.1 写的是“建议的时刻,没有就是两小时后、明早 09:00”(照
respond.mjs的scheduleHTML)。在 00:00 出“两小时后 02:00”不合常理,改成:有建议时刻用建议,没有就给接下来的三个早上 09:00,只留问题截止之前的(composite 的“后刹排气定哪天?”在 8/13 00:00 给出今早、明早、周六)。 - 换轮时字一律在切点换。 3.2 写了切点硬切,实现里第一版沿用 D 的
morph,期限与日期在 0.56 秒交叉淡入,和记录不符;改成换轮时每个部件都在切点换成新字(keep全部),只有盒子与位置在动。 - 下沉的回执一路变形。 D 让卡在下沉的前半段保持卡的样子、后半段才变成行(
leadFace);托盘的回执和“已收到”的行本来就像,前半段保持回执宽度时,新的一行字会伸出到右栏刻度上,所以盒子随下沉一路从回执变成行。 - 大数字旁的字跟数字同一刻翻。 第一版里“M 个问题”在切点就换了,大数字要到 0.20 秒才翻,中间一瞬是“6 下答完 4 个问题”。现在旁边一行在大数字翻的那一帧硬翻;下面的拆法先淡出旧句(0.10 秒)再淡入新句(0.20 秒),两句不叠在一起(叠化时两段不同的句子会糊成一片)。
- 新问题的朱红外框。 第一版把描框的延迟写在列表上,之后每一拍改这个延迟,已经描过的外框会再亮一次;改成写在每个托盘上、描完就去掉这一类。外框从内阴影改成 1.5 px 的边框(同样只动不透明度,画起来更省)。
- 长按大数字的概要原想把全页口径整段放进去,太长,一瞥读不完;只留拆法与一句“托盘几排就是几下:确认、批准、选择 1 下,推后、定时刻 2 下”,整段口径在页脚与详情里。
- 讲一遍中的轻点也只用来跳过:它的点按不会再落到托盘的按钮上替你作答(键盘一直如此)。
- 没有做的。 金额没有本页的数字键盘(只有预填与建议金额,2.1);“挑日子”“自定义”用浏览器自带的日期控件;桌面上没有悬停出概要(这是手机页面,鼠标按住 0.45 秒同样出概要)。
6 交付(ship)
6.1 性能
无头 Chrome(打开 GPU),手机 390 × 844、3 倍像素、触屏,composite 数据;每一拍从触发起量 requestAnimationFrame 间隔,sync 是切点上那一次同步工作(补丁、量位置、建动画)。下表是 CPU 4 倍降速下的一次(三次之间切点长帧在 50–233 ms 之间浮动,其余不变):
| 节拍 | sync ms | 帧数 | fps | p95 ms | 最长一帧 ms |
|---|---|---|---|---|---|
| 换轮 f24 → f25 | 23 | 81 | 61.8 | 16.7 | 16.8 |
| 叠起 f25 → f26(两个新问题进来) | 67 | 80 | 60.9 | 16.8 | 83(切点) |
| 按下一下(批准) | 46 | 56 | 62.1 | 16.8 | 50(切点) |
| 两下的第一下(推到…) | 40 | 44 | 62.2 | 16.7 | 50(切点) |
| 第二下(挑一天) | 23 | 57 | 62.8 | 16.7 | 16.8 |
| 撤回 | 21 | 45 | 63.5 | 16.7 | 16.8 |
| 让位 f26 → f27(作答后) | 43 | 80 | 61.3 | 16.8 | 50(切点) |
| 换回 f26 | 34 | 81 | 61.7 | 16.8 | 33(切点) |
| 轻点托盘展开详情 | 73 | 48 | 59.6 | 16.8 | 100(切点) |
| 收回详情 | 7 | 44 | 62.2 | 16.7 | 16.8 |
| 长按出概要卡 | 16 | 26 | 62.6 | 16.8 | 33(切点) |
| 收起概要卡 | 2 | 26 | 63.1 | 16.8 | 16.8 |
| 讲一遍 composite(7.7 秒) | — | 460 | 59.7 | 16.8 | 50(第一个切点) |
| 讲一遍 story(6.3 秒) | — | 376 | 59.5 | 16.8 | 67(第一个切点) |
不降速时每一拍都是 61–63 fps,没有一帧超过 16.8 ms,sync 0–12 ms。和 D 一样,唯一的长帧在切点上,之后全程 16.7 ms 一帧。
一处没完全弄清:新问题外框还是内阴影的时候,4 倍降速下 f25 → f26 的第一次落地有 3 / 5 次在拍中(约 0.66 秒,新托盘揭开的那一刻)出现一个 150–700 ms 的长帧,脚本剖析里这段没有脚本在跑,回到 f26 的第二次落地从来没有;外框改成边框之后连量三次都没再出现。也可能是那时同一台机器上别的检查在跑,或是新字形第一次光栅化,真机上要再看(6.3)。
6.2 检查
node scripts/check-ix.mjs --only trays:composite 29 项、story 27 项全过(story 没有两下的托盘,那三项换成一项“每个托盘一排,几下等于几个”)。项目:带?frame=落地不讲、不动;托盘高 = 头部 + 剩下几下 × 46 px、大数字 = 各托盘几下之和;长按出概要、点空白收起、移动取消、点概要卡长出详情、Esc 收回且焦点回到托盘;轻点托盘长出详情、短下拉弹回、长下拉收回、‹ 收回;作答收成回执且数字减一、焦点到撤回、手指保护、撤回;两下的第一下矮一排、× 退回、第二下收成回执;连播中长按停住、→ 立即收起并换轮;Tab 出概要、Enter 打开、Esc 收回;换轮后回执落到“已收到与关上”、左滑 50 px 弹回、过 80 px 知道了;直接打开讲一遍、轻点跳到终态;减弱动态时不讲。- 全部页面两套数据也各跑了一遍:都过。第一遍里 C(composite)、原版树图与回应托盘(story)各有一两项计时类的没过(同一台机器上别的检查在同时跑),单独重跑都过。
- 逐帧扫过两份数据的每一轮(composite 29 轮、story 37 轮):每一轮每个托盘的高都只有 107 px(一排)或 153 px(两排),合计等于大数字;在几个最忙的轮次把每个问题都答掉(一下、两下的推后与定时刻、要写一句的、金额、照片),数字都归零、没有报错。
node scripts/shoot-forms.mjs --only trays,all --gpu --check all-sync:两套数据都过,合页里回应托盘跟着顶栏的帧走。npm run site:build通过,体验站预览图site/previews/trays.webp照工作环境说明的参数截(f26,收起概念卡)。- 截图(只在本机):f26 第一眼、按下“批准”后的回执、两下托盘点了第一下、f26 → f27 让位途中、落定后的 f27、长按概要卡、展开的详情,均为 390 × 844。
6.3 只能在真机上核的
- Safari iOS 上各拍是否同样只在切点有长帧(Web Inspector 时间线),以及 6.1 那个第一次落地的长帧会不会出现。
- 长按:
-webkit-touch-callout: none之后会不会仍选字或出放大镜;松手后补发的点按(按会补发处理,150 ms 内吞掉)。 - 托盘列是页内滚动区:到顶下拉时 Safari 的回弹与详情的下拉收回是否打架;左缘右滑收回详情(
pushState)在真机上的手感。 - “挑日子”“自定义”在 iOS 上弹出系统的日期滚轮,盖住托盘时的样子。