mcpField
A
animate-onboarding

animate-onboarding

github.com/superwall设计与 UI3010 次安装今天更新
代码仓库

关于 animate-onboarding

用于为引导流程实现有意义动效的 skill,面向使用 Superwall surface 的开发者。它梳理页面间的转场,并以共享几何框架实现元素变形,使形状在动画过程中保持对齐。

主要功能

  1. 01梳理页面转场中的元素
  2. 02将所选答案变形成目标元素
  3. 03用单一框架承载交叉淡化内容
  4. 04协调动效与路由转场
  5. 05处理前进及反向转场

适用场景

  1. 01将答案动画变形为进度条
  2. 02将所选选项变形成页面图像
  3. 03保持返回导航动效一致
  4. 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.md
handoffs.md
imageReady.ts
morphFrame.ts
performance.md
references/framework/
answers.ts
flow.ts
handoffs.ts
layout.tsx
motion.css
Question.tsx
README.md
useStoryRouter.ts
references/web-runtime/
handoffs.ts
README.md
story-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(目标元素)——按以下顺序选择:

  1. 下一屏上与故事相关、视觉效果最突出的元素。 如果选项 A 是紫色,而下一屏是紫色背景,那么背景就是一个很好的目标。
  2. 主视觉图片。 让已选选项或继续按钮变形为下一屏的主视觉图片,总是不错的选择。
  3. 拿不准时,选择进度条:它能让选择过程更有游戏感,并提高完成率。
  4. 或者选择下一个继续按钮:这样会让用户想要再次按下它。

返回时反转角色。 如果前进时三个答案合并变成一张照片,那么返回时,三个答案都要从这张照片中出现——而不只是已选答案出现,其他答案照常向上进入。

在代码中验证映射。 默认情况下,每个元素都会执行标准的进入动画,因此,如果某个元素标记为“变形自”,但缺少对应的豁免设置,它就会同时执行进入动画和变形——两种动画会相互冲突。对于两个方向的每次交接,都要确认每个目标元素的标准进入动画已关闭(预先声明: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、宽度/高度动画、未加限制的悬停效果、未支持减少动态效果,以及所有元素同时入场)。

工作流

  1. 侦察。 找出页面入口(config.ts、app/ 中每页一个路由、带进度条和返回按钮的 layout.tsx)、答案组件、页面标题组件、所有图片,以及现有的缓动/时长令牌。阅读每一页的文案,并划分故事线的幕。如果页面切换发生在 router.push/router.back 以外的任何位置,先修复。
  2. 先用表格规划,然后停下。 每次交接占一行:从哪里 → 到哪里、移动什么、为什么适合这对页面——按照上述优先级选择 A 和 B。表格下方列出每次交接双向的元素映射(说明每个元素的作用),再列出整个流程的规则。最多提出一个决策问题,并给出建议。获批后再构建。
  3. 构建引擎(engine.md,从 references/framework/ 开始):添加选择启用的标记、为每页添加一个 story-… 过渡名称、在路由层添加保持状态,将 router.push/router.back 封装起来,使交换发生在交接过程中(并等待目标页面的图片加载),在布局中添加动画层,并预先声明目标页面。
  4. 构建变换和交接(handoffs.md):先实现答案 → 进度条的飞行动画,再实现 A → B 变形,然后实现每组页面的退出动画,最后通过 CSS 变量覆盖实现进入动画。
  5. **展示给任何人之前,完成映射检查、几何检查、时间线检查和性能检查:**每个“变换自”元素的进入动画都必须关闭;每个 A → B 都必须经过 morph()(一帧),并且起止位置必须与实际矩形完全一致;最后一帧之前不得显示目标页面,也不得让目标页面淡入并覆盖其文案;任何动画元素都不能是 position: fixed。**时间线:**每次交接都要列出屏幕上每段文案的显示时段,以及其下方每个进入动画的时间。任何进入动画都不得在覆盖它的文案显示期间运行(全屏颜色帧会覆盖整个页面),每段文案都必须在自身变换结束时移除,而不是等到进入动画结束。然后再处理 performance.md。之后在真实的 iPhone 上测试。
  6. 为用户运行。 执行 superwall dev,然后点击工作室的 Preview 按钮生成二维码,在用户手机上打开流程;评估流畅度应以生产构建(superwall push,然后使用工作室中推送的版本)为准。没有让用户亲自体验之前,不要声称效果合适。
  7. 逐轮调整体验,每轮只改一项,并提供数据。 根据实际关键帧计算表格(t、ms、尺寸、圆角、位置、不透明度、模糊),并在报告前验证各项不变量(尺寸下限、无超调、速度连续性、起止两端静止)。自查时以 2–5× 速度播放,或逐帧查看。
  8. 在标记后启用并发布,确保旧动画仍可访问(网页漏斗使用 motion 查询键;原生端使用常量或 placement 参数),以便比较漏斗流失率。只有在用户要求时才执行 superwall push;由用户推广。

评论

支持 Markdown