You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Expo React Native SVG:自定义汉堡菜单 onPress 动画实现求助

解决React Native汉堡菜单转叉号的动画实现问题

嘿,我明白你遇到的问题了——用setNativeProps搞汉堡菜单转叉号的动画确实容易踩坑,尤其是在onPress里控制序列动画的时候。我给你分享一个用官方Animated库实现的靠谱方案,能完美实现你要的效果:先收缩中间线条,再让上下线条移动形成叉号。

为什么setNativeProps不适合这个场景?

  • setNativeProps是直接操作原生视图属性,跳过React的状态更新流程,虽然性能好,但很难精准控制**动画序列(先收缩中间线,再移动上下线)**的执行顺序
  • 在onPress中直接调用setNativeProps,容易因为React的异步更新机制导致动画状态不同步,出现部分生效的情况

推荐方案:使用React Native Animated库实现

Animated是官方推荐的动画库,能完美处理序列动画、并行动画,还能和React状态联动,下面是完整的实现代码:

1. 基础组件结构与状态定义

首先导入需要的模块,定义状态和动画值:

import React, { useState, useRef } from 'react';
import { View, TouchableOpacity, Animated } from 'react-native';

const HamburgerMenu = () => {
  // 控制菜单展开/收起状态
  const [isOpen, setIsOpen] = useState(false);
  
  // 动画值定义:中间线条宽度、上下线条的Y偏移
  const middleLineWidth = useRef(new Animated.Value(24)).current;
  const topLineY = useRef(new Animated.Value(0)).current;
  const bottomLineY = useRef(new Animated.Value(0)).current;
  
  // 线条的基础样式
  const lineStyle = {
    width: 24,
    height: 2,
    backgroundColor: '#000',
    borderRadius: 1,
    position: 'absolute',
    left: 0,
  };

2. 实现 onPress 中的动画逻辑

在点击事件里,根据当前状态执行对应的动画序列:

const handlePress = () => {
    if (!isOpen) {
      // 从汉堡菜单转为叉号:先收缩中间线,再移动上下线
      Animated.sequence([
        // 第一步:中间线条收缩到0
        Animated.timing(middleLineWidth, {
          toValue: 0,
          duration: 200,
          useNativeDriver: false, // 因为修改的是width属性,不支持原生驱动
        }),
        // 第二步:同时移动上下线条到目标位置
        Animated.parallel([
          Animated.timing(topLineY, {
            toValue: 12, // 对应你要的y2=18,假设初始top是6,偏移量为12
            duration: 200,
            useNativeDriver: false,
          }),
          Animated.timing(bottomLineY, {
            toValue: -12, // 对应你要的y2=6,假设初始top是18,偏移量为-12
            duration: 200,
            useNativeDriver: false,
          }),
        ]),
      ]).start(() => setIsOpen(true));
    } else {
      // 从叉号转回汉堡菜单:先移动上下线回原位,再恢复中间线宽度
      Animated.sequence([
        Animated.parallel([
          Animated.timing(topLineY, {
            toValue: 0,
            duration: 200,
            useNativeDriver: false,
          }),
          Animated.timing(bottomLineY, {
            toValue: 0,
            duration: 200,
            useNativeDriver: false,
          }),
        ]),
        Animated.timing(middleLineWidth, {
          toValue: 24,
          duration: 200,
          useNativeDriver: false,
        }),
      ]).start(() => setIsOpen(false));
    }
  };

3. 渲染动画视图

把动画值绑定到对应的线条样式上:

return (
    <TouchableOpacity 
      onPress={handlePress}
      style={{ width: 24, height: 20, position: 'relative' }}
    >
      {/* 顶部线条 */}
      <Animated.View
        style={[
          lineStyle,
          { top: 6, transform: [{ translateY: topLineY }] },
        ]}
      />
      {/* 中间线条 */}
      <Animated.View
        style={[
          lineStyle,
          { top: 9, width: middleLineWidth },
        ]}
      />
      {/* 底部线条 */}
      <Animated.View
        style={[
          lineStyle,
          { top: 18, transform: [{ translateY: bottomLineY }] },
        ]}
      />
    </TouchableOpacity>
  );
};

export default HamburgerMenu;

关键细节说明

  • 动画序列控制:用Animated.sequence确保先执行中间线收缩,再执行上下线移动;用Animated.parallel让上下线的移动动画同时进行
  • 状态联动:动画完成后再切换isOpen状态,避免状态和动画不同步
  • 样式适配:根据你实际的线条初始位置调整top值和偏移量,我这里假设三条线的初始top分别是6、9、18,你可以根据自己的布局修改
  • useNativeDriver:因为我们修改的是width和top(通过transform的translateY),其中width不支持原生驱动,所以这里设置为false;如果只做transform的动画可以开启原生驱动提升性能

可选优化:添加回弹效果

如果想让动画更自然,可以把Animated.timing换成Animated.spring,比如:

Animated.spring(topLineY, {
  toValue: 12,
  stiffness: 300,
  damping: 30,
  useNativeDriver: false,
})

这样动画会有轻微的回弹,交互体验更好。

内容的提问来源于stack exchange,提问作者TheBag

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:39:34