关于 animate-onboarding
用于为引导流程实现有意义动效的 skill,面向使用 Superwall surface 的开发者。它梳理页面间的转场,并以共享几何框架实现元素变形,使形状在动画过程中保持对齐。
主要功能
- 01梳理页面转场中的元素
- 02将所选答案变形成目标元素
- 03用单一框架承载交叉淡化内容
- 04协调动效与路由转场
- 05处理前进及反向转场
适用场景
- 01将答案动画变形为进度条
- 02将所选选项变形成页面图像
- 03保持返回导航动效一致
- 04在 Superwall surface 中实现转场
MCP 相关
这是一个 skill,并非 MCP 服务或客户端;输入未说明其具备 MCP 功能。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ superwall/ skills --skill animate-onboarding安装后在 Claude Code 中输入/animate-onboarding确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/engine.mdhandoffs.mdimageReady.tsmorphFrame.tsperformance.mdreferences/framework/answers.tsflow.tshandoffs.tslayout.tsxmotion.cssQuestion.tsxREADME.mduseStoryRouter.tsreferences/web-runtime/handoffs.tsREADME.mdstory-motion.css详细介绍
让新手引导动起来
让新手引导流程动得更有意义。页面、答案胶囊、进度条和按钮都已存在;此技能只改变它们的运动方式——让读者感受到问题讲述的故事,也让答案上的轻触成为推动流程前进的可见动作。
流程所在的位置:Superwall 界面
新手引导是团队上线后仍会想要调整的屏幕——重新排列问题、删去一个步骤、尝试新的陈述——而无需发布应用。这正是 superwall framework 的用途:流程是 superwall/ 中的一个界面(config.ts,以及 app/ 中每个屏幕对应的一个页面,layout.tsx 中的共享界面元素),可通过 superwall dev 在每台设备上预览,以不可变版本推送,并从控制面板切换为线上版本。本技能中的所有内容都基于这种结构,下面的引擎也映射到了它的路由器:
- 页面对应一条路由,所有页面切换都由路由器负责
(
router.push、router.back、来自superwall/navigation的useRouter())。 切勿构建页面切换器、自定义路由器或克隆即将离开的页面:离开的页面在动画播放期间仍处于挂载状态,因此无需克隆。交接时唯一的脚本是叠加在路由器转场之上的共享元素飞行动画(references/framework/useStoryRouter.ts中的useRouter轻量封装,用于测量、调用router.push,然后再次测量)。 - 交接是进入页面上的自定义转场名称(
export const transition = "story-picture"),通过[data-sw-route]上路由器的四个阶段(enter、recede、leave、return)进行样式设置。 - 进度条、返回按钮和主按钮都位于
layout.tsx中,因此选定的答案有一个始终存在的目标可供飞入。 - 第一页的入场动画以界面呈现为门槛(
useSuperwallSnapshot ().paywall !== undefined),而不是以挂载为门槛:SDK 会在隐藏状态下预加载界面。
修改流程前,请先阅读 superwall-framework 技能(如果尚未安装,请运行 npx skills add https://github.com/superwall/skills --skill superwall-framework --global --yes --agent claude-code universal --full-depth 安装);其中的 references/mobile-design.md 包含本技能所依据的触控、动效和排版规则。如果流程还不是一个界面(例如原生新手引导或手工搭建的 Web 漏斗),可以先建议将其改为界面(原生屏幕可使用 superwall create 或 superwall migrate --screen <path>);由用户决定。只有用户坚持使用自己的 Web 运行时,才适用 references/web-runtime/,且绝不适用于界面。
设计方向源自 Emil Kowalski 的设计工程实践和 Apple 的流体界面(两者都整合在 mobile-design.md 中)、交接动画应与其连接的两个页面文案紧密关联这一理念,以及对一个已上线漏斗(Reflective 的发现流程)进行多轮直接反馈的经验。以下方向就是这些反馈的结论。其优先级高于一般审美偏好。
唯一规则:Frame A → Frame B 始终共享同一套几何属性
当元素 A 转变为元素 B 时,在过渡的每一帧中,A 和 B 都必须具有完全相同的几何属性——尺寸、位置、圆角半径、模糊效果。不是“差不多”,也不是“由同一组数值驱动”,而是完全相同。
两个独立的动画副本(A 和 B 各一个),各自拥有剪裁、变换和模糊效果,必然会产生偏差:A 自身的圆角端点、边框、边距、不同的模糊效果或舍入差异。眼睛会立刻察觉到 1px 的不匹配,整个形变就会显得出了问题。
因此,每个 A → B 都要构建为一帧、两张面(references/morphFrame.ts):由单个元素拥有所有几何属性(剪裁是唯一的轮廓,只有一个变换、一个模糊效果),A 和 B 则是填充其中、仅通过交叉淡入淡出切换的两张面。它们不可能在几何属性上有所不同,因为几何属性只有一套。
const answer = fillFace(answerEl, answerRect); // capture A before the page swaps
// …swap the page…
morph({
from: { rect: answerRect, radius: answerRect.height / 2 },
to: { rect: photoRect, radius: 20 },
faces: { from: answer, to: photoFace(photoEl) },
duration: 504,
});
面:photoFace(img)、fillFace(el, rect)(答案、按钮:其颜色填满整个框架,内容停留在原位)、colorFace(color)(背景)。每种情况的实现方案都在 references/handoffs.md 中。绝不要手动将 A → B 形变实现为两个图层。
检查方法:框架的起始位置和圆角必须与 A 的矩形和圆角完全一致,结束位置和圆角必须与 B 的完全一致,这样交接给实际元素时才能做到像素级精确。
参考资料——阅读与相关部分对应的内容:
references/framework/— 已交付的 superwall 框架上的引擎,见animated-onboarding示例(运行superwall create --example animated-onboarding即可获取完整项目):handoffs.ts(两个方向的每次交接及清理)、useStoryRouter.ts(交接过程中的router.push/back)、motion.css(路由器各阶段的退出和进入效果)、layout.tsx(界面外壳和动画图层)、Question.tsx、flow.ts。在 surface 上从这些内容开始。references/engine.md— 框架路由器上的交接引擎:图层保持、router.push外围的flushSync、动画图层、布局中的界面外壳、焦点陷阱、标记、预先声明的角色、已加载的目标、打断、中断处理陷阱。references/handoffs.md— 如何构建变换(答案 → 进度条、答案 ↔ 照片、进度条 → 答案)、时钟和交接目录。references/performance.md— 哪些内容要做动画,哪些绝不能做,以及如何诊断“动画卡顿”。references/morphFrame.ts— A → B 形变:一帧、两张面、滑动时钟、形状。所有元素到元素的变换都要使用它。references/imageReady.ts— 包括 Safari 在内的图像就绪检测。references/web-runtime/— 用户在手写的 Web 运行时中保留流程时使用的同一引擎(该运行时没有路由器,因此会克隆离开的页面)。在 surface 上绝不要从这里开始。
方向(来自反馈)
1. 先绘制每个元素的映射,再编排动画
编写任何动画之前,先为每个页面上的每个元素绘制映射,涵盖两个方向的每次交接,并为每个元素分配且仅分配一个角色。元素是否相同,取决于它们在视觉上是否等效——也就是看起来是否像同一个东西、处于同一位置,而不是它们是否真的是同一个组件。务必读懂字里行间的意思。
| 从 → 到 | 动画 |
|---|---|
| A → A,位置和尺寸相同 | 不添加动画。 两者都不执行退出或进入过渡。给它添加动画只会让人注意到一个没有变化的元素(例如连续页面上固定不动的“继续”按钮)。 |
| A → A,位置、尺寸或文本不同 | 平滑地退出,再进入。 |
| A → B(不同的元素) | 变形本身就是过渡。 A 不退出,B 不进入;不能让其他动画与之争抢注意力。 |
| A → 无 | 缩小、淡出并模糊消失。让元素依次变化,使它们看起来像是被其他元素带动的——细微一些,像多米诺骨牌。尽可能加入物理效果:向后倒下时带上倾斜或透视。 |
| 无 → B | 通常采用上述效果的反向思路。 |
选择 A(源元素)——用户最后交互的元素优先级最高:即用户在单选中选中的选项。继续按钮的优先级最低。对于多选,让所有已选选项都变形进入进度条。
选择 B(目标元素)——按以下顺序选择:
- 下一屏上与故事相关、视觉效果最突出的元素。 如果选项 A 是紫色,而下一屏是紫色背景,那么背景就是一个很好的目标。
- 主视觉图片。 让已选选项或继续按钮变形为下一屏的主视觉图片,总是不错的选择。
- 拿不准时,选择进度条:它能让选择过程更有游戏感,并提高完成率。
- 或者选择下一个继续按钮:这样会让用户想要再次按下它。
返回时反转角色。 如果前进时三个答案合并变成一张照片,那么返回时,三个答案都要从这张照片中出现——而不只是已选答案出现,其他答案照常向上进入。
在代码中验证映射。 默认情况下,每个元素都会执行标准的进入动画,因此,如果某个元素标记为“变形自”,但缺少对应的豁免设置,它就会同时执行进入动画和变形——两种动画会相互冲突。对于两个方向的每次交接,都要确认每个目标元素的标准进入动画已关闭(预先声明:engine.md,“预先声明角色”)。
2. A → B 变换如何运动
它应该感觉流畅而有实体感,像一个连续的整体。
- 所有堆叠阶段共用一个时钟。 变换由多个堆叠阶段组成(抬升、移动、展开、落位)。让每个通道——位置、大小、圆角、交叉淡化、模糊——都由同一个时钟驱动,这样第 1 阶段平滑启动,每个后续阶段都以承接上一阶段的速度和加速度开始,最后一个阶段平滑结束。让每个阶段各自使用独立的缓动区间,会使运动在每个边界处停顿。移动先行,展开随后沿同一条曲线进行(例如大小按
p^1.5变化),绝不从静止状态重新开始。 - 时钟是临界阻尼弹簧(阻尼 1.0,这是 Apple 对 UI 的默认值),用于它驱动的每个维度(位置和大小):先有几乎感觉不到的回拉(约 1%),随后在前段迅速升至最高速度(约为持续时间的 27%),然后用漫长且持续减速的尾段抵达目标——中间没有匀速段,结尾没有过冲或弹跳。两端都从静止开始和结束(最后的渐缓会让它精确落在最后一帧)。通过数值检查:处于接近最高速度的时间 ≤ 总时长的约 15%,过冲为 0,最后四分之一逐渐柔和地减速。
- 路径要柔和。 以正常斜率抵达目的地,并在接近目的地时线性增大尺寸;陡峭的末段曲线或乘法式放大会让结尾过于急促,并放大速度变化。
- 尺寸下限:任何时候都不能比较小的一端小超过 20%。 80 → 100 的过程中最低可以到 64,绝不能到 20。不要把一个回答压缩成缩略图。(例外:回答变成 4px 进度条的一段时,必须缩小到能成为进度条。)
- 气泡感: 如果某个维度在两端的尺寸大致相同(例如两端宽度都是 348px),就在运动过程中最多缩小 20%(按正弦形变化),再膨胀回来——像气泡一样挤过去。如果两端尺寸相差一半或更多,则不做这种变化。
- 采用单一几何规则(见上文):只有框架的裁剪区域能构成轮廓。绝不对圆角形状进行非均匀缩放(这会把圆形端点压成椭圆);由框架进行裁剪(
inset(... round r)),并且只向上缩放。 - 圆角按比例计算。 将圆角半径插值为形状较短边的比例(即其圆度:0.5 = 完全圆形端点),而不是按像素计算;起点使用源形状本身的圆度,终点使用目标形状的圆度。完全圆形的回答展开为方形全屏背景时,即使放大后也应保持明显的圆角,直到最后才变成方形;如果按像素插值,大框架几乎立刻就会显得圆角很小、边缘很尖。
- 模糊就是运动模糊。 它从清晰开始,也以清晰结束,只在框架移动最快时产生模糊:持续时间的 25% 到 75%(在 25–35% 之间渐入,35–65% 期间屏幕上的模糊量保持为 5px,在 65–75% 之间渐出)。两个面共用一个模糊效果,放在框架的外层包装器上(不做变换),这样它会按屏幕像素柔化轮廓。交换(交接)发生在此时间窗口内,因此 A 会在模糊下变成 B。单一颜色进入进度条(或离开进度条)时没有东西需要拖影:
blur: 0。为所有运动模糊保留一个开关(morphFrame.ts中的MOTION_BLUR_ENABLED),这样可以在一个地方关闭它,以便单独排查问题。 - 返回绝不是倒放。 返回是使用同一时钟的独立运动,落位时同样保持漫长柔和的尾段,中途不改变方向。例如,从进度条返回时,药丸形元素会带走进度条中属于自己的那一段(进度条会在药丸形元素下方立即让出),随后滑出、下落并放大为回答。
- 所有 A → B 变换使用相同的持续时间——参考实现中为 504ms。它们曾逐渐变成 720–820ms,显得拖沓;请保持时长一致且轻快。
3. 交接:不重影、不闪烁、不缺目标
-
当某个元素正在变形为目标元素时,目标元素绝不会淡入。 保持实时元素隐藏;由变形副本一路承载它。
-
在最后一帧(已经绘制)时交接。 在整个变形过程中,将实时元素保持在
opacity: 0.001(已绘制,但不可见);变形结束时让它恢复到 1,并在同一帧移除副本。浏览器不会绘制完全透明的元素,因此如果将元素保持在 0,直到最后一帧才显示,就会出现一帧空白:也就是闪烁。 -
目标必须已加载。 未加载的
<img>没有高度,因此变形会测量到零尺寸的目标,并播放空白动画。提前解码每张图片并保留它;交接到包含图片的页面前,等待图片加载完成(最多约 400ms;较新的点击会取消等待);如果图片仍未就绪,则跳过变形,并在图片加载后淡入。 -
Safari(所有 iPhone 上)在创建新的
<img>时,即使图片已缓存,也会异步加载:页面切换后,新图片会立即显示为“尚未加载”,高度为零。 因此:- 运行变形时,绝不要要求新的
<img>已加载——只要求流程自身解码后的副本已就绪(如果以实时元素为条件,iPhone 上的变形就永远不会运行); - 根据已知的自然尺寸设置图片的
width/height,这样图片绘制前就能获得正确的尺寸; - 根据已经解码的
Image对象创建副本,而不是对<img>使用cloneNode()(克隆会重新加载并绘制空白); - 在实时图片实际加载完成前,让副本保持在上层;
- 在图片的
load事件中将图片标记为就绪;较旧版本的 Safari 会拒绝对文档外的图片调用decode()。 请在真实 iPhone 上测试:这些问题都无法在桌面版 Chrome 中复现。
- 运行变形时,绝不要要求新的
-
每个副本都在自身变形结束时移除。 移除副本时,只需等待副本的动画、目标元素的 0.001 保持状态(它们会在同一帧结束),以及实时照片加载完成。绝不要等待新页面的入场动画:否则副本会在整个动画期间遮挡它们。有一次,副本被全屏颜色遮挡层覆盖,语句中的文字因此隐藏了约 1 秒,遮挡层消失后文字才突然出现。保留两个列表(
engine.md):一个包含所有元素,以便较快的二次点击能立即结束动画;另一个包含交接对象,用于清理。 -
不要让任何元素在遮挡副本下入场。 变形帧会一直覆盖实时页面,直到交接。任何在此之前开始入场的元素(例如全屏颜色上的文字、切换到其上的页眉)都会在不可见的情况下淡入,并在变形帧消失时于淡入过程中突然显现。应在
TRANSFORM_MS之后再启动这些元素的入场动画。
3b. 全屏颜色时刻(陈述屏幕)
在全屏答案颜色背景上呈现一句「你知道吗……」陈述,是问题之间的有力节奏点。它的背景是屏幕上视觉冲击力最强的元素,而且正是答案本身的颜色,因此根据目的地规则:
- 问题 → 陈述:所选答案展开为背景。 一帧之内,答案面变为颜色面,圆角从胶囊的完整圆弧逐渐变方,直到最后才完全呈直角。答案不会飞入进度条:此处背景是更合适的目的地(进度条照常推进)。
- 颜色是独立的全屏图层,由页面背后的运行时渲染(而非页面或 body 的背景),因此它可以作为同一元素的变形目的地和来源,并在交接前保持不可见(0.001)。
- 文字在颜色到达后出现(入场延迟约 540ms:在 504ms 的帧完成交接后立即出现,绝不提前),标题和按钮也反色融入背景,只在颜色到达后切换。
- 陈述 → 下一个问题:颜色像答案胶囊一样准确进入进度条(参见 4):它的右边缘飞向进度条末端,同时缩成与进度条等高的胶囊,接上后与进度条一起增长到新的长度,并弹跳一下。陈述转化为进度。陈述文字先后逐个离开,飞到颜色上方。
- 返回就是胶囊的回程:进度条的延伸段沿着它滑出,随后下落并扩展为全屏颜色;颜色再收拢回打开它的答案。
3c. 浏览器自身的界面(iOS 26 工具栏着色)——仅适用于 Web 漏斗
SDK 的 webview 中没有浏览器界面;此处跳过本节,并且绝不要在原生界面中添加 position: fixed 元素(遵循框架的内边距规则)。但在 Safari 中运行的 Web 漏斗需要注意:iOS 26 Safari 会忽略 <meta name="theme-color">。它会从接触顶部边缘的 position: fixed 元素着色(距顶部约 4px 以内、宽度至少占 80%、高度至少 3px,且具有自身的背景色);否则就使用页面背景色。全屏颜色扫入时会导致工具栏在过渡中途重新着色,看起来很不协调。因此:
- 使用哨兵固定工具栏色调:在最顶部放置一个永久固定的元素,高 4px、占满全宽、颜色与页面一致,并置于最上层。Safari 始终从它获取色调。保留
theme-color以兼容 Chrome 和旧版 Safari。 - 绝不要使用
position: fixed制作动画。 每个副本、帧和飞行中的胶囊都使用position: absolute,并置于相同的屏幕坐标(页面本身不会滚动)。全屏固定的变形帧正是 Safari 获取色调的来源:它曾让工具栏闪现陈述颜色。哨兵必须是唯一的固定元素。 - 要在手机上验证,可在测试时将哨兵设为红色:工具栏应变红,并在每次过渡期间保持红色。
4. 标志性动作:答案变成进度
- 每个选中的答案都会飞进进度条——每个问题页面都是如此,除非它正在变形成更好的东西(下一页的主视觉)。
- 增长进度条的是选择,而不是计时器。 在答案出现之前,进度条保持不动;答案出现后,两者同步移动,由同一条采样时间线驱动。使用猜测延迟的 CSS 过渡不够好。
- 右边缘对右边缘。 答案围绕其右边缘缩小,该边缘飞向进度条当前的末端;两者合并,然后一起增长到新的长度。不是“像砖头一样掉进空隙”。
- 路径:一起上升并漂移,上升领先;移动到约 87% 的路程时到达进度条的高度,然后保持水平滑过最后一小段。
- 增长后弹跳一次(约 13%,ease-out-back)。
- 进度条除了长度之外什么都不变——不动画颜色或不透明度(胶囊在合并前就变为进度条的颜色,移除时不做淡出),高度也不变(膨胀会看起来像闪烁,因为上方的胶囊不会随之膨胀)。
- 填充部分在裁剪轨道内通过
translateX滑动,绝不使用scaleX,这样圆润的末端才能保持圆润。 - 任何进入进度条的东西都用同样的方式——包括全屏的声明色:右边缘飞向进度条的末端,缩小成进度条高度的胶囊,合并,一起增长,然后弹跳。通过单一几何帧和自定义路径(
morph({ path }))实现,绝不拉伸形状(将全屏矩形缩放到 4px 会压扁它的圆角)。进度条是尺寸下限的唯一例外。 - **返回:**参见上文“返回绝不是倒退”。
5. 效果出色的部分
- “一天内消失”:旧页面淡出至全无,空白停留一拍(约 120ms),然后照片从
scale(1.03)稳稳出现;退出时先抬起照片,再抬起文字。 - 欢迎页面的退出——A → 空无的物理效果示例:它在上浮时向后倾斜
(
perspective(900px) translateY(-20px) rotateX(22deg) scale(.97),绕底边旋转并淡出),就像一张卡片向后倾倒。 - 根据文案为每一对页面选择交接方式(遗忘相关页面向外漂移,“重新解释”从上方进入,焦躁感通过更快的切换呈现,最后一个答案完成进度条后交接到“全部准备就绪”)。
6. 尝试过但已否决的方案——不要再考虑这些
| 尝试过的方案 | 失败原因 |
|---|---|
| 使用 View Transitions API 快照实现退出和变形 | 快照伪影(边缘模糊、变形拉伸),每帧都要重新合成整页快照 |
对每条进入的文字、答案和图片应用 filter: blur() | 软件栅格化时约 15fps;文字周围出现模糊光晕 |
胶囊答案移动时使用 backdrop-filter | 每次移动都要重新计算 |
| Genie / 网格“被吸进栏中”(切成条带) | 看起来不够高级;设计过于取巧 |
| 让药丸旋转并朝向移动方向 | 效果不理想;沿直线移动并配合恰当时机就可以 |
| 添加会随过渡变化的装饰性背景图案 | 装饰元素抢了主体内容的风头 |
| 让答案像砖块一样填满栏的缺口(从左边缘延伸到栏末端) | 看起来像是在放置,而不是推进 |
| 答案加入时让栏的高度膨胀 | 像闪了一下:上方的药丸没有随之膨胀 |
| 让飞行中的药丸在栏上方淡出 | 栏的颜色变化清晰可见 |
| 让照片从一个较大的源矩形中淡入 | 出现重影 |
| 将答案缩成缩略图,再与照片交换 | 太小;角被压扁;过渡不流畅 |
使用原生 easeInOutBack | 回拉 10% 并过冲,中段太急:显得突兀 |
| 使用僵硬的模拟弹簧 | 过冲约 6%,膨胀后在约 75ms 内弹回 |
| 使用平滑步进缓动,在 82% 处过冲 1.5%,再于约 90ms 内回稳 | 中段太线性,结尾太有弹性、太急促 |
| 为每个阶段分别设置缓动区间 | 每到阶段边界都会停顿 |
| 变换到位时让目标淡入 | 元素会出现两次 |
| 返回时反向播放前进动画 | 方向在运动中途翻转;没有真实的着陆感 |
| 到最后一帧才显示目标 | 绘制前会闪现一帧 |
| 将 A 和 B 作为两个独立的动画层,各自设置裁剪、变换和模糊,“由同一组数值驱动” | 几何形状发生偏差:A 自身的圆角端点、不同的模糊和取整方式;变形过程中尺寸明显不同 |
等待新的 <img> 加载后再触发变形 | 在 iOS Safari 上从未运行(缓存图片会异步加载) |
| 以像素为单位插值圆角半径 | 大画面很快就会出现极小、看起来很尖锐的圆角 |
| 将声明文字的颜色通过普通变形缩入栏中,变成新的片段 | 应像药丸一样移动:右边缘移至栏末端,接合后一起扩展 |
使用 position: fixed 动画副本和变形帧 | iOS 26 Safari 会根据顶部固定元素调整工具栏色调:声明文字的颜色闪进了浏览器顶部栏 |
| 让模糊效果与交换同步,在交换过程中增强,并持续淡出到最后一帧 | 只有前半段有模糊。模糊是运动模糊:两端清晰,25% 至 75% 之间模糊 |
| 等到所有动画(包括新页面的进入动画)结束后才移除副本 | 全屏颜色帧在声明文字的整个进入动画期间遮住文字:长时间停留在红色,然后文字突然出现 |
| 让声明文字在约 420ms 时开始进入,而画面帧持续 504ms | 最初约 84ms 的动画不可见,因此画面帧消失时,文字会突然在淡入过程中出现 |
当用户否决某个方案时,彻底还原到最后一个获批状态(通过构建输出确认),然后再尝试下一个方案。不要在已否决的方案上叠加修复。
原则
克制优先。 引导流程是初次使用时才会遇到的稀有界面——可以多花些心思带来惊喜——但每个动效仍需有明确目的:解释、空间一致性、状态指示、反馈。键盘导航(Enter/Esc)和浏览器前进/后退使用朴素的 200ms 淡入淡出:它们会重复出现,或由系统触发动画,因此不应精心编排。
路由器负责导航。 每次页面交接都是由路由器执行的自定义转场(export const transition = "story-…", CSS 应用于 data-sw-transition 和 data-sw-phase);脚本只负责共享元素的飞行动效,放在 useRouter 的包装器中。自定义路由器、页面切换器、克隆页面或 position: fixed 副本都不应出现在界面中,无论它们能让什么变得更容易。
每次页面交接都要匹配页面组合。 阅读页面 A 和页面 B 的文案,选择能表达页面 B 内容的动效。在代码注释中写明每组页面交接的设计理念。较长测验中,一系列相关的题目 → 题目交接可以采用同一套动效;但不同环节的边界应有各自鲜明的时刻。按环节调整节奏:平和、柔和、在紧张时加快,在结尾时归于平稳。
流畅,而非预设脚本。 按下指针时立即响应。每次导航都会立即完成上一次页面交接,因此快速连续点击时无需等待。动效应暗示目的地(在页面打开前就开始移动)。
工艺(mobile-design.md 中的 Motion 部分)。
- 曲线:
--ease-out: cubic-bezier(0.23, 1, 0.32, 1)、--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1)。UI 上绝不使用ease-in。 - 退出比进入更快:退出用时 120–380ms,进入用时 240–720ms;A → B 的变换使用一个共享且利落的时长。
- 入场顺序:标题,然后副标题,再然后答案,每项间隔 40ms(30–80ms);答案从第一帧起即可点击。
- 绝不从
scale(0)开始;初始缩放比例至少为 0.9,并搭配透明度变化。 - 动效只改变
transform和opacity,但单个元素每次只能使用以下两种特许例外之一:变形的clip-path和 5px 模糊。 - 减少动态效果模式下只改变透明度,不移动元素。
- 对照
mobile-design.md中的禁止发布清单自查(transition: all、scale(0)、ease-in、宽度/高度动画、未加限制的悬停效果、未支持减少动态效果,以及所有元素同时入场)。
工作流
- 侦察。 找出页面入口(
config.ts、app/中每页一个路由、带进度条和返回按钮的layout.tsx)、答案组件、页面标题组件、所有图片,以及现有的缓动/时长令牌。阅读每一页的文案,并划分故事线的幕。如果页面切换发生在router.push/router.back以外的任何位置,先修复。 - 先用表格规划,然后停下。 每次交接占一行:从哪里 → 到哪里、移动什么、为什么适合这对页面——按照上述优先级选择 A 和 B。表格下方列出每次交接双向的元素映射(说明每个元素的作用),再列出整个流程的规则。最多提出一个决策问题,并给出建议。获批后再构建。
- 构建引擎(
engine.md,从references/framework/开始):添加选择启用的标记、为每页添加一个story-…过渡名称、在路由层添加保持状态,将router.push/router.back封装起来,使交换发生在交接过程中(并等待目标页面的图片加载),在布局中添加动画层,并预先声明目标页面。 - 构建变换和交接(
handoffs.md):先实现答案 → 进度条的飞行动画,再实现 A → B 变形,然后实现每组页面的退出动画,最后通过 CSS 变量覆盖实现进入动画。 - **展示给任何人之前,完成映射检查、几何检查、时间线检查和性能检查:**每个“变换自”元素的进入动画都必须关闭;每个 A → B 都必须经过
morph()(一帧),并且起止位置必须与实际矩形完全一致;最后一帧之前不得显示目标页面,也不得让目标页面淡入并覆盖其文案;任何动画元素都不能是position: fixed。**时间线:**每次交接都要列出屏幕上每段文案的显示时段,以及其下方每个进入动画的时间。任何进入动画都不得在覆盖它的文案显示期间运行(全屏颜色帧会覆盖整个页面),每段文案都必须在自身变换结束时移除,而不是等到进入动画结束。然后再处理performance.md。之后在真实的 iPhone 上测试。 - 为用户运行。 执行
superwall dev,然后点击工作室的 Preview 按钮生成二维码,在用户手机上打开流程;评估流畅度应以生产构建(superwall push,然后使用工作室中推送的版本)为准。没有让用户亲自体验之前,不要声称效果合适。 - 逐轮调整体验,每轮只改一项,并提供数据。 根据实际关键帧计算表格(t、ms、尺寸、圆角、位置、不透明度、模糊),并在报告前验证各项不变量(尺寸下限、无超调、速度连续性、起止两端静止)。自查时以 2–5× 速度播放,或逐帧查看。
- 在标记后启用并发布,确保旧动画仍可访问(网页漏斗使用
motion查询键;原生端使用常量或 placement 参数),以便比较漏斗流失率。只有在用户要求时才执行superwall push;由用户推广。