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

React Native使用Hooks实现按钮边框双线条无限循环流动动画

React Native Hooks实现按钮边框循环流动线条动画

核心实现思路

  • 采用两个独立的Animated.Value作为动画进度驱动,初始相位错开半个周期,保证两条线条不会重合
  • 用Animated.loop包裹线性变化的进度动画,实现无限循环播放
  • 通过interpolate插值方法,将0~1的进度值映射为线条的位置、宽高样式,匹配边框四个边的移动路径
  • 所有动画值用useRef持有,避免不必要的组件重渲染

完整可运行代码

import React, { useEffect, useRef } from 'react';
import { View, Animated, Easing, TouchableOpacity, Text, StyleSheet } from 'react-native';

// 常量配置,可自行调整
const BUTTON_WIDTH = 220;
const BUTTON_HEIGHT = 60;
const LINE_LENGTH = 80; // 流动线条的长度
const LINE_COLOR = '#2196F3';
const ANIMATION_DURATION = 4000; // 单次循环的时长

const App = () => {
  // 两个独立的动画进度值,第二个初始进度为0.5,错开相位
  const progress1 = useRef(new Animated.Value(0)).current;
  const progress2 = useRef(new Animated.Value(0.5)).current;

  // 启动无限循环动画的方法
  const startLoopAnimation = (progress) => {
    return Animated.loop(
      Animated.timing(progress, {
        toValue: 1,
        duration: ANIMATION_DURATION,
        easing: Easing.linear, // 线性运动保证速度均匀
        useNativeDriver: false,
      })
    ).start();
  };

  useEffect(() => {
    // 组件挂载后启动两个线条的动画
    startLoopAnimation(progress1);
    startLoopAnimation(progress2);
  }, []);

  // 生成线条样式的公共方法
  const getLineStyle = (progress) => {
    // 插值映射X轴偏移
    const translateX = progress.interpolate({
      inputRange: [0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1],
      outputRange: [
        0, BUTTON_WIDTH - LINE_LENGTH, // 顶部:从左往右移动
        BUTTON_WIDTH - 2, BUTTON_WIDTH - 2, // 右侧:X固定
        BUTTON_WIDTH - LINE_LENGTH, 0, // 底部:从右往左移动
        0, 0 // 左侧:X固定
      ]
    });

    // 插值映射Y轴偏移
    const translateY = progress.interpolate({
      inputRange: [0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1],
      outputRange: [
        0, 0, // 顶部:Y固定
        0, BUTTON_HEIGHT - LINE_LENGTH, // 右侧:从上往下移动
        BUTTON_HEIGHT - 2, BUTTON_HEIGHT - 2, // 底部:Y固定
        BUTTON_HEIGHT - LINE_LENGTH, 0 // 左侧:从下往上移动
      ]
    });

    // 插值映射线条宽度
    const lineWidth = progress.interpolate({
      inputRange: [0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1],
      outputRange: [LINE_LENGTH, LINE_LENGTH, 2, 2, LINE_LENGTH, LINE_LENGTH, 2, 2]
    });

    // 插值映射线条高度
    const lineHeight = progress.interpolate({
      inputRange: [0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1],
      outputRange: [2, 2, LINE_LENGTH, LINE_LENGTH, 2, 2, LINE_LENGTH, LINE_LENGTH]
    });

    return {
      position: 'absolute',
      backgroundColor: LINE_COLOR,
      width: lineWidth,
      height: lineHeight,
      transform: [{ translateX }, { translateY }]
    };
  };

  return (
    <View style={styles.container}>
      {/* 按钮容器,相对定位 */}
      <View style={{ width: BUTTON_WIDTH, height: BUTTON_HEIGHT, position: 'relative' }}>
        {/* 两条流动线条 */}
        <Animated.View style={getLineStyle(progress1)} />
        <Animated.View style={getLineStyle(progress2)} />
        {/* 实际的按钮内容 */}
        <TouchableOpacity style={styles.button}>
          <Text style={styles.buttonText}>点击按钮</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#f5f5f5'
  },
  button: {
    width: '100%',
    height: '100%',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'white',
    borderRadius: 4,
    borderWidth: 1,
    borderColor: '#eee'
  },
  buttonText: {
    fontSize: 16,
    color: '#333'
  }
});

export default App;

关键说明

  • 可以修改常量配置里的参数,调整线条长度、颜色、动画速度,也可以给两条线条设置不同的颜色区分
  • 如果要优化性能开启原生驱动,可以将宽高的动态切换改成transform: [{scaleX}]和transform: [{scaleY}]实现,不需要修改布局属性即可开启useNativeDriver: true
  • 原有按钮的点击逻辑、样式都可以正常加在内部的TouchableOpacity上,不会和动画冲突

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 14:24:00