用途
animate-expo 是一个用于在 React Native 和 Expo 中构建动画的技能。它把“想要动效”的请求转化为能在真机上通过严格审查的实现——不是在模拟器里,也不是在开发模式下的旗舰手机上。该技能的知识来自 Emil Kowalski 的动画理念。
移动端改变了动画的三件事,技能中的所有内容都由此展开:
- 没有悬停。Web 上所有依赖悬停的交互提示,在移动端必须落在按压、位置或什么都不做上。
- 有两个运行时。Worklets(Reanimated 4)让这一点变得明确:React Native 运行时负责 React 渲染和应用逻辑,UI 运行时负责 worklet 逐帧运行(另有可选的 worker 运行时处理后台工作)。触碰 RN 运行时的动画会在应用做其他事情时卡顿。整个技艺的核心是让动效留在 UI 运行时。
- 用户的手指就在元素上。手势是主要输入,因此可中断性和速度交接不是润色,而是基线。
主要能力
该技能提供一套按顺序执行的构建流程:
- 判断是否应该动画。按使用频率分级:每天 100 次以上(标签切换、键盘开合、滚动、设置开关)不做动画;每天数十次(按压反馈、列表导航、行选择)只做几乎不可察觉的动画(150ms 以内)或不做;偶尔使用(面板、模态、吐司、引导步骤)使用标准动画;罕见/首次(成功状态、空状态插画、庆祝)才使用“愉悦预算”。标签切换永不滑动,使用 animation: 'none'。
- 明确目的。用一个词命名:反馈、空间一致性、状态指示、防止突兀变化、解释或愉悦(仅罕见层级)。无法命名就不构建。
- 选择工具——最便宜且能用的。按需向下查找,停在第一个合适的工具:状态驱动无手势变化用 Reanimated CSS transition;循环、多阶段或挂载播放用 Reanimated CSS animation;元素挂载/卸载或列表重排用布局动画;手指触碰或滚动派生用 useSharedValue + Gesture + useAnimatedStyle;屏幕间导航用 Expo Router 的原生栈选项;作为独立屏幕的底部面板用 presentation: 'formSheet';标签栏用 NativeTabs;上下文菜单和长按预览用 Link.Menu / Link.Preview(仅 iOS);折叠成大标题的头部用 headerLargeTitleEnabled(仅 iOS);下拉刷新用 RefreshControl;跟随键盘的 UI 用 react-native-keyboard-controller;矢量插画、庆祝、空状态用 Lottie;大型动画场景和自由绘制用 @shopify/react-native-skia。
- 选择属性。transform 和 opacity 是免费的,其他都会触发布局。唯一例外是无子元素的绝对定位元素(如标签胶囊、进度条填充)。永远不要 scale(0),从 scale(0.9–0.97) + opacity: 0 开始。transform 是数组且顺序重要,translate 放在前面。Android 阴影是 elevation,动画 elevation 会每帧重渲染阴影,改为对预阴影层做透明度交叉淡入。永远不要动画 BlurView 的 intensity。translate 中百分比相对于元素自身尺寸。
- 时序还是弹簧。如果涉及手指,用弹簧;弹簧能在中断时传递速度,时序曲线会重新开始。其他情况用时序。Reanimated 的弹簧直接接受 Apple 的两个设计参数,使用 { duration, dampingRatio } 形式,而非 mass/stiffness/damping。缓动方面:进入或退出用 ease-out;屏幕上移动/变形用 ease-in-out;恒定运动用 linear;默认用 ease-out。永远不要在 UI 上用 ease-in。提供了具体的贝塞尔曲线常量。时长:按压反馈 100–150ms;开关、chip、小状态变化 150–200ms;面板、模态、抽屉用弹簧,感知约 300ms;屏幕过渡用平台默认,不要覆盖。
- 保持在 JS 线程之外。永远不要在手势或滚动处理器中 setState。永远不要在 onUpdate 或滚动处理器中调度回 RN 运行时。永远不要在渲染期间读取或写入共享值。使用 .get() / .set() 而非 .value。从 worklet 调用的函数需要 'worklet' 作为第一行。
- 按压而非悬停。按压时反馈,松开时提交。任何可按压元素用 scale: 0.97,100–150ms。最小触摸目标 44×44pt(Android 48dp),视觉更小则加 hitSlop。使用 pressRetentionOffset。Android 波纹只在 Material 风格应用中使用。
- 触觉反馈。在值越过步进、吸附归位、重物落地、操作成功或失败时使用。三条绝对规则:与视觉同帧;每个用户操作一次;永远不是唯一反馈。从 worklet 中需通过 scheduleOnRN 调度触觉。
- 减少动态效果与无障碍。使用 useReducedMotion 和 ReduceMotion.System。减少动态意味着更少更温和,而非零:保留解释状态变化的透明度和颜色变化,去掉位移、缩放、视差和过冲。屏幕过渡变为 animation: 'fade'。文本会缩放,永远不要动画到硬编码高度。
使用方式
该技能在首次被调用且没有具体问题时,只回复一句就绪声明,不提供其他信息,直到用户提问。之后按构建序列执行,直接写代码,用一行说明理由,绝不把动效选项作为菜单呈现。输出时先写代码,然后用最多几行说明:门控结果(频率层级和命名目的,说明拒绝了什么及原因)、成分(工具、属性、弹簧或曲线加时长、线程)、需要在设备上感受检查的内容(手势、速度交接和触觉时序无法从代码判断)。代码就是交付物,不要扩写成报告。
依赖安装使用 npx expo install <package>,它会解析与项目 SDK 匹配的版本。所需包包括:动画用 react-native-reanimated + react-native-worklets;手势用 react-native-gesture-handler;导航、面板、原生标签、菜单用 expo-router;触觉用 expo-haptics;跟随键盘的 UI 用 react-native-keyboard-controller(需要在根部放置 KeyboardProvider);插画和庆祝用 lottie-react-native;大型动画场景和自定义绘制用 @shopify/react-native-skia。
适用场景
适用于在 React Native 和 Expo 中构建移动端动画,包括按压反馈、拖拽关闭面板、滑动删除、折叠头部、列表进入、键盘同步 UI、标签指示器、屏幕过渡等。资料提到 RECIPES.md 包含可直接构建的实现,当请求匹配某个配方时应加载它,从配方开始而非空白文件。
限制
该技能有明确的“永不发布”清单,例如:不用 PanResponder 而用 Gesture.Pan();不在手势或滚动处理器中 setState;不用已弃用的 runOnJS 而用 scheduleOnRN;不逐帧调度 scheduleOnRN;不在渲染期间读写共享值;手指触碰的内容不用核心 Animated;不动画 height/width/margin/flex/top;不动画 BlurView intensity 或 Android elevation;不在虚拟化列表行上使用 entering;不用 JS 重建屏幕过渡;不在标签间滑动;UI 元素不用 Easing.in;不用 scale(0) 进入;不用仅距离的关闭阈值;不在边界硬停;不逐帧触觉或把触觉作为唯一反馈;不在 Expo Go 或模拟器中判断手感。
设置方面需注意:通过 Expo 安装以匹配 SDK 版本;Expo 项目中 babel-preset-expo 自动配置 worklets Babel 插件;GestureHandlerRootView 必须包裹应用;Reanimated 4 需要新架构;Expo Go 不是性能环境,应在发布构建中判断手感;ProMotion iPhone 上第三方动画默认限制在 60fps,除非设置 CADisableMinimumFrameDurationOnPhone,近期 Expo SDK 默认设置,需确认存在。
关于该技能的收费、价格、平台支持范围等更多信息,资料暂未提供。