尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

react-native-reanimated 中的 withDecay:基于速度的衰减滚动动画完全指南

react-native-reanimated 中的 withDecay:基于速度的衰减滚动动画完全指南 react-native-reanimated 中的 withDecay基于速度的衰减滚动动画完全指南【免费下载链接】react-native-reanimatedReact Natives Animated library reimplemented项目地址: https://gitcode.com/GitHub_Trending/re/react-native-reanimated导读withDecay是 react-native-reanimated 提供的基于初始速度的“滚动”式动画 API它模拟物体在摩擦作用下的自然减速过程是构建列表惯性滚动、卡片滑动释放等交互场景的核心工具。本文以官方文档v2.x 版本文档 withDecay.md为骨架结合当前仓库中withDecay的完整源码实现、运行时测试用例详细讲解其全部配置参数、数学原理、橡皮筋rubber band回弹机制以及实际接入手势的完整方案读完即可在自己的项目中写出可落地的惯性滚动动画。一、withDecay 是什么一次理解“速度驱动的衰减动画”withDecay的定位在文档中只有一句话启动一个基于速度的 “scroll” 动画。这意味着动画的驱动力不是某个目标值像withTiming/withSpring那样收敛到终点而是初始速度——动画开始后速度随时间指数衰减物体随之滑行直到速度趋近于零从而产生“甩出去后自然停下”的惯性效果。从当前仓库的源码结构看withDecay的实现位于 packages/react-native-reanimated/src/animation/decay/decay.ts并对外通过 packages/react-native-reanimated/src/animation/decay/index.ts 导出。整个 decay 模块包含四个核心文件文件职责decay.tswithDecay入口合并默认配置、校验参数、选择刚体衰减或橡皮筋衰减rigidDecay.ts刚体衰减速度按指数曲线衰减撞到 clamp 边界立即停止rubberBandDecay.ts橡皮筋衰减越过 clamp 边界后产生回弹再归位utilsCommon.ts类型定义、默认配置与常量SLOPE_FACTOR、VELOCITY_EPSwithDecay典型的应用场景是与手势库配合用户在拖拽结束时传入手指的瞬时速度物体继续滑行并可用clamp限制滑行范围。官方文档给出的示例就是PanGestureHandlerwithDecay的组合。二、参数详解六个配置项逐一拆解withDecay接收两个参数配置对象options和可选的完成回调callback。配置对象支持的所有参数及其默认值如下表格内容与 v2.x 文档完全一致并补充了当前源码中的类型约束参数默认值说明velocity0初始速度单位像素/秒deceleration0.998衰减速率越接近 1 衰减越慢、滑行越远clamp[]两个动画边界组成的数组可选例如[0, 200]velocityFactor1修改速度单位的因子可选rubberBandEffectfalse到达 clamp 边界后的橡皮筋回弹效果可选rubberBandFactor0.6橡皮筋回弹力的修正因子可选velocity初始速度动画的起点速度单位默认是像素每秒px/s。在手势场景中通常直接取手势事件的velocityX/velocityY。deceleration衰减速率控制速度下降的快慢。源码中速度的计算公式为v(t) initialVelocity * exp(-(1 - deceleration) * t * SLOPE_FACTOR)其中SLOPE_FACTOR定义在 utilsCommon.ts值为0.1。可以看到deceleration越接近 1(1 - deceleration)越小指数衰减越平缓物体滑行距离越远、时间越长反之衰减越快、滑行越短。默认值0.998是一个接近 1 的数值能产生平滑自然的惯性滑行。clamp边界限制clamp是一个长度为 2 的数组[min, max]将动画结果限制在指定范围内。注意当前仓库的DecayConfig类型见 utilsCommon.ts规定clamp是[min: number, max: number]元组并且开启rubberBandEffect时clamp是必填项类型层面已经强制约束。运行时的参数校验也印证了这一点——在 decay.ts 的validateConfigworklet 函数中若传入了clamp必须是数组且长度必须为 2否则抛出[Reanimated] \clamp array must contain 2 items... 错误velocityFactor必须大于 0使用rubberBandEffect时必须同时设置clamp否则抛出[Reanimated] You need to set \clamp property when using rubberBandEffect. 错误。velocityFactor速度单位缩放这是文档单独用一个小节强调的参数。decay 默认的速度单位是“像素/秒”但当你需要动画的值并非像素而是归一化区间时例如透明度[0, 1]、进度条[0, 1]像素/秒 的量级就过大。此时可传入 1的velocityFactor来缩放速度使其适配目标值域。从 rigidDecay.ts 的实现可见每帧位移的计算为current v * velocityFactor * deltaTime / 1000velocityFactor直接作为速度的乘法缩放因子参与位移计算。rubberBandEffect / rubberBandFactor橡皮筋回弹默认的刚体衰减rigidDecay在撞到clamp边界时会立即停住。而开启rubberBandEffect: true后动画改用 rubberBandDecay.ts 中的rubberBandDecay实现物体越过边界后产生一个与越界深度成正比的“弹簧拉力”由rubberBandFactor控制强度将物体拉回边界表现为橡皮筋式的来回回弹最终停在边界上。rubberBandFactor越大回弹越猛烈、越“硬”。源码中的判断逻辑decay.tsconst decay isValidRubberBandConfig(config) ? (animation, now) rubberBandDecay(animation, now, config) : (animation, now) rigidDecay(animation, now, config);即满足“rubberBandEffect为真且clamp是长度为 2 的数组”utilsCommon.ts 的isValidRubberBandConfig时自动切换到橡皮筋模式。源码中的隐藏参数reduceMotion在 v2.x 文档的参数表中没有列出但当前仓库源码的DecayConfig类型还支持reduceMotion?: ReduceMotion选项默认值为ReduceMotion.System用于响应系统的“减弱动态效果”无障碍设置。从 decay.ts 看当动画处于 reduceMotion 模式且设置了clamp时起始值会被直接吸附到最近的边界上从而跳过惯性滑行。三、callback 回调与返回值callback可选withDecay(config, callback)的第二个参数是完成回调动画自然结束时回调收到true若动画被取消例如被新的动画打断或cancelAnimation取消回调收到false。在 decay.ts 返回的动画对象中callback被原样挂载到动画对象上由动画系统的运行循环在动画结束onFrame返回true或取消时触发。返回值withDecay返回一个动画对象。它有两种典型用法直接赋值给 Shared Valuex.value withDecay({ velocity: 900, clamp: [0, 200] });作为useAnimatedStyle返回值的一部分——这也是文档示例采用的方式且官方运行时测试 basic.test.tsx 正是在useAnimatedStyle内直接调用withDecay(config)作为样式值const animatedStyle useAnimatedStyle(() { return { left: withDecay(config), }; });提示动画对象可以赋值给useAnimatedStyle内任意需要动画的属性位移、透明度、宽高等不仅限于本文示例中的transform.translateX。四、官方文档示例手势惯性滚动完整可运行文档给出了一个完整的惯性拖拽示例。为了让组件真实渲染出来此处将文档片段补全为可运行的最小实现核心代码与文档一致import Animated, { useSharedValue, withDecay, useAnimatedStyle, useAnimatedGestureHandler, } from react-native-reanimated; import { PanGestureHandler } from react-native-gesture-handler; import { StyleSheet, View } from react-native; function App() { const x useSharedValue(0); const gestureHandler useAnimatedGestureHandler({ onStart: (_, ctx) { ctx.startX x.value; }, onActive: (event, ctx) { x.value ctx.startX event.translationX; }, onEnd: (evt) { x.value withDecay({ velocity: evt.velocityX, clamp: [0, 200], // optionally define boundaries for the animation }); }, }); const animatedStyle useAnimatedStyle(() { return { transform: [ { translateX: x.value, }, ], }; }); return ( PanGestureHandler onGestureEvent{gestureHandler} Animated.View style{[styles.box, animatedStyle]} / /PanGestureHandler ); } const styles StyleSheet.create({ box: { width: 100, height: 100, backgroundColor: midnightblue, }, }); export default App;流程拆解onStart记录拖拽起点ctx.startX x.valueonActive跟随手指位移x.value ctx.startX event.translationXonEnd手势结束时读取事件的velocityX手指松开的瞬时速度作为velocity传给withDecay同时用clamp: [0, 200]把滑行范围限制在 0200 像素内useAnimatedStyle将x.value映射到translateX动画直接在 UI 线程执行不经过 JS 桥。如果希望拖拽结束时带一点橡皮筋手感只需把配置改为x.value withDecay({ velocity: evt.velocityX, clamp: [0, 200], rubberBandEffect: true, rubberBandFactor: 0.6, });五、源码级原理衰减数学与帧循环细节每帧位移公式刚体衰减 rigidDecay.ts 的核心逻辑const deltaTime Math.min(Math.max(now - lastTimestamp, 0), 64); const v velocity * Math.exp(-(1 - config.deceleration) * (now - startTimestamp) * SLOPE_FACTOR); animation.current current (v * config.velocityFactor * deltaTime) / 1000; animation.velocity v; animation.lastTimestamp now;值得注意的实现细节帧间隔钳制deltaTime被限制在[0, 64]毫秒避免掉帧如切后台导致单帧位移过大产生跳变指数衰减速度基于自startTimestamp起的累计时间按指数衰减而不是每帧递减固定值这使得衰减曲线平滑连续、与帧率无关终止条件Math.abs(v) VELOCITY_EPS时动画结束其中VELOCITY_EPS 1 / 20定义在 utils.ts即速度低于 0.05 px/帧量级时判定为“停下”clamp 命中即停initialVelocity 0且越过clamp[0]或initialVelocity 0且越过clamp[1]时将current钉在边界值并立即返回true结束动画。橡皮筋衰减的回弹逻辑rubberBandDecay.ts 在指数衰减速度之外叠加了一个与越界深度成比例的“弹簧力”const derivative (current clamp[0] || current clamp[1]) ? current - clamp[clampIndex] : 0; const v velocity * Math.exp(...) - derivative * config.rubberBandFactor;derivative表示当前值越过最近边界的深度当越界深度|derivative| DERIVATIVE_EPS0.1时springActive置为true物体被“拉回”回弹结束后物体被精确吸附到clamp[clampIndex]边界上并结束动画。初始化细节decay.ts 中的注释特别说明动画对象的current初始值必须设为undefined而不是0否则会破坏动画的初始化流程——这是 reanimated 动画系统中onStart接管初始值设定的约定了解这一点有助于排查“动画起始值异常”的问题。六、配置校验规则速查防坑指南根据 decay.ts 的validateConfig以下写法会在运行时抛出错误非法配置错误信息clamp不是数组config.clamp must be an array but is ...clamp长度不为 2clamp array must contain 2 items but is given NvelocityFactor 0config.velocityFactor must be greater than 0 ...rubberBandEffect: true但未设clampYou need to set clamp property when using rubberBandEffect其中后两条也在DecayConfig的 TypeScript 联合类型utilsCommon.ts中做了静态约束开启rubberBandEffect的分支强制要求clamp必填、并允许rubberBandFactor可选不开则二者皆可选。七、测试用例佐证各配置组合的行为快照当前仓库为withDecay提供了运行时测试 apps/common-app/runtime-tests/reanimated/tests/animations/withDecay/basic.test.tsx通过 mock 动画计时器逐帧记录动画更新并与快照比对。测试覆盖了以下 7 组典型配置可作为参数调优的参考基准test.each([ { velocity: 900 }, { velocity: 9, velocityFactor: 100 }, { velocity: 900, deceleration: 0.997 }, { velocity: 900, clamp: [0, 150] }, { velocity: 900, clamp: [0, 150], rubberBandEffect: true }, { velocity: 2000, clamp: [0, 150], rubberBandEffect: true }, { velocity: 2000, clamp: [0, 150], rubberBandEffect: true, rubberBandFactor: 2, }, ] as ArrayWithDecayConfig)(Config ${0}, async (config) { // 记录每帧动画更新与 Snapshot 快照比对 });这几组用例恰好覆盖了本文讲到的全部参数维度纯速度、velocityFactor缩放等价性velocity: 9velocityFactor: 100与velocity: 900的位移曲线应当一致、不同deceleration、纯 clamp、以及不同速度/不同rubberBandFactor下的橡皮筋回弹行为。如果你在调参时不确定某个组合的效果可以直接对照该测试文件中的快照数据验证自己的推导。此外withDecay在 mock.ts 中也有对应的 mock 实现便于在 Jest 等测试环境中直接使用而无需真机动画帧。八、与其他动画 API 的配合与选型withDecay与withTiming/withSpring的关键区别在于“没有明确终点”withTiming需要指定目标值匀速/缓动到达withSpring需要指定目标值带弹簧回弹withDecay只需初始速度滑行多远由deceleration和clamp决定。因此在“列表惯性滚动”“卡片滑动释放后继续飞行”“环形菜单甩动”这类由手势速度驱动的物理感交互中withDecay是首选而需要精确落到某个位置如吸附到某个分页索引时常与withSpring/withTiming组合使用——例如先withDecay滑行滑行结束后在回调里再用withSpring吸附到最近栅格。总结withDecay是 react-native-reanimated 中实现惯性物理感的核心 API。本文从官方 v2.x 文档出发完整覆盖了其六个配置参数velocity、deceleration、clamp、velocityFactor、rubberBandEffect、rubberBandFactor的语义与默认值并深入到当前仓库源码解释了指数衰减公式、帧间隔钳制、终止阈值、橡皮筋回弹机制与参数校验规则最后通过官方运行时测试用例给出了可直接参考的调参组合。掌握这些细节你就能在列表滚动、卡片拖拽、进度指示器等场景中写出流畅且边界正确的惯性动画。相关资源速览官方文档v2.xdocs/docs-reanimated/versioned_docs/version-2.x/api/animations/withDecay.md核心实现packages/react-native-reanimated/src/animation/decay/decay.ts刚体衰减packages/react-native-reanimated/src/animation/decay/rigidDecay.ts橡皮筋衰减packages/react-native-reanimated/src/animation/decay/rubberBandDecay.ts类型与常量packages/react-native-reanimated/src/animation/decay/utilsCommon.ts运行时测试apps/common-app/runtime-tests/reanimated/tests/animations/withDecay/basic.test.tsx【免费下载链接】react-native-reanimatedReact Natives Animated library reimplemented项目地址: https://gitcode.com/GitHub_Trending/re/react-native-reanimated创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表