Pressto源码深潜:BaseButton+SharedValue实现60fps按压反馈的核心原理

📅 2026/8/26 14:45:55
Pressto源码深潜:BaseButton+SharedValue实现60fps按压反馈的核心原理
Pressto源码深潜BaseButtonSharedValue实现60fps按压反馈的核心原理【免费下载链接】presstoYour React Native app deserves better tap interactions.项目地址: https://gitcode.com/gh_mirrors/pre/presstoPressto 是一个 React Native 开源库用运行在 UI 线程的动画按压组件替代 TouchableOpacity实现 60fps 的流畅按压反馈。本文带你深潜 Pressto 源码讲透 BaseButton 与 SharedValue 如何协作看懂丝滑按压背后的完整原理。一、为什么普通 TouchableOpacity 会掉帧先弄清问题出在哪React Native 的动画如果跑在JS 线程上一旦 JS 线程被业务逻辑、网络请求、复杂渲染占满动画帧率就会骤降——按钮按下去卡顿一下就是典型的丢帧表现。Pressto 的思路很直接把动画整体搬到 UI 线程。它基于 react-native-reanimated 的 Worklet 机制 react-native-gesture-handler 的 BaseButton 构建按下到回弹的全程不经过 React 重渲染从而稳定跑到 60fps。二、整体架构从入口到按钮的 3 层结构 Pressto 源码结构非常精简核心就在src/目录下三层各管一件事PressableScale / PressableOpacity ← 第3层内置视觉反馈interpolate 插值 │ createAnimatedPressable ← 第2层HOC注入你的 animatedStyle │ BasePressable ← 第1层状态机 动画编排核心 │ BaseButton (RNGH) SharedValue (Reanimated)入口 src/index.tsx 只导出两行pressables 与 provider 两个模块核心状态机在src/pressables/base.tsx全文约 400 行本文主角HOC 工厂在src/pressables/hoc.tsx内置的缩放/透明度按钮在src/pressables/custom/scale.ts与src/pressables/custom/opacity.ts三、BaseButton手势识别的地基Pressto 没有自己实现触摸检测而是直接复用 react-native-gesture-handler 的BaseButton并用 Reanimated 包一层const AnimatedBaseButton Animated.createAnimatedComponent(BaseButton);这一行见src/pressables/base.tsxL20是整个库的地基。BaseButton负责把手指按下/抬起翻译成可靠的状态事件而Animated.createAnimatedComponent让它能直接接收由 SharedValue 驱动的动态 style。BaseButton 的事件与 Pressto 的处理映射如下BaseButton 事件含义Pressto 的动作onPress完成一次有效点击active false翻转isToggled记录最后触摸者onBegan/onActivated手指按下active true触发onPressInonEnded/onFailed/onCancelled抬起 / 滑出 / 取消active false触发onPressOut这种映射base.tsxL384-L398带来两个好处反馈即时只要手指落下哪怕还没松开按钮就进入按压态自动处理列表冲突因为站在 BaseButton 肩上Pressto 与 RNGH 的FlatList天然做了 tap 冲突检测避免了点列表项却闪烁高亮行的经典问题。四、SharedValue动画状态的主线程载体这是 60fps 的秘密所在。BasePressable里所有驱动动画的状态全部是 Reanimated 的SharedValueconst active useSharedValue(false); // 是否正在按压 const progress useDerivedValue(() // 0 → 1 的动画进度 withAnimationConfigured(active.get() ? 1 : 0) );SharedValue 的数据本体存放在UI 线程的 Native 侧JS 线程对它只有写入意图真正的逐帧计算弹簧、时序插值在 UI 线程独立完成。所以active翻转只是一个跨线程信号不触发任何 React 重渲染progress是一个派生值目标从 0 变 1 后由withTiming或withSpring在 UI 线程里逐帧推进JS 线程再忙动画帧也不受影响——这就是它比 TouchableOpacity 稳的根源。默认动画参数定义在src/provider/constants.tstiming 为 250ms 贝塞尔缓动spring 为damping: 30, stiffness: 200。⚠️ 注意所有驱动动画的函数都必须以worklet指令开头否则代码不会编译到 UI 线程动画会静默失效。Pressto 专门提供了 ESLint 插件eslint-plugin-pressto/rules/require-worklet-directive.js在开发期帮你拦截这个高频错误。五、useAnimatedStyle把进度翻译成视觉 状态有了最后一步是用useAnimatedStyle把progress翻译成样式。这个 worklet 在 UI 线程随帧执行const rAnimatedStyle useAnimatedStyle(() animatedStyle(progress.get(), { isPressed, isToggled, isSelected, ... }) );内置按钮的实现极其优雅——scale.ts全文只有几行transform: [{ scale: interpolate(progress, [0, 1], [config.baseScale, config.minScale]) }]interpolate把 0→1 的进度线性映射到缩放值默认 1 → 0.96opacity.ts同理把透明度映射到 1 → 0.5。由于interpolate本身也是 worklet逐帧插值也发生在 UI 线程链路完整闭环。六、PressablesConfig一处配置全局生效src/provider/index.tsx提供了全局 Provider把动画策略下沉为配置而非代码animationTypetiming或spring切换整个应用的回弹手感config全局默认视觉值activeOpacity: 0.5 / minScale: 0.96 / baseScale: 1单个按钮可用configprop 浅合并覆盖一项globalHandlers例如全局挂载触感反馈haptic单个按钮可用skipGlobalHandlers退出metadata类型安全地把设计系统主题带入 worklet。此外PressablesGroup通过一个lastTouchedPressableSharedValue 在组内广播谁被最后按了派生出isSelected状态——连选中态也是纯 UI 线程计算不经过 React 状态。七、3 步接入 60fps 按压反馈 ✅原理讲完上手只需 3 步第 1 步 · 安装依赖bun add pressto react-native-reanimated react-native-gesture-handler react-native-worklets第 2 步 · 替换按钮import { PressableScale } from pressto; PressableScale onPress{() alert(Pressed!)} TextPress me/Text /PressableScale第 3 步可选· 全局调参用PressablesConfig包一层设置animationTypespring和config全应用按钮立刻换成弹簧手感。想要旋转、颜色渐变等自定义反馈用createAnimatedPressable传入一个带worklet指令的函数拿到 0→1 的progress自由插值即可。完整示例可参考example/目录下的演示应用。结语Pressto 的核心并不复杂却把每一环都放对了位置BaseButton 负责可靠的手势语义SharedValue 把状态搬到 UI 线程useAnimatedStyle interpolate 完成逐帧渲染。三层各司其职JS 线程彻底退出动画链路60fps 自然水到渠成。下次你的 React Native 按钮出现按感迟滞时这套源码级的思路值得直接借鉴。【免费下载链接】presstoYour React Native app deserves better tap interactions.项目地址: https://gitcode.com/gh_mirrors/pre/pressto创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考