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

React Native Animated实现CSS关键帧动画的时间序列方案咨询

Converting CSS @keyframes Animations to React Native's Animated API for a Bouncing Circle Spinner

I get it—translating CSS keyframe animations to React Native's Animated API can feel tricky, especially when you have multi-stage, time-sequenced effects like your bouncing circle spinner. Let's break down how to replicate both your orbit and circle animations exactly as they work in CSS, using React Native's interpolation and timing tools.

First, let's recap the core requirements from your CSS:

  • Orbit animation: Stay at scale 1 for the first half, then cycle between scale 0.8 and 1 twice before finishing.
  • Circle animation: Gradually fill the border from top → right → bottom, then scale up/down twice, finally reset to the initial border state.

Here's the complete, working React Native implementation that matches your original CSS:

/** @flow **/
import React, { useEffect, useState } from 'react';
import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet';
import { Animated, Easing, StyleSheet } from 'react-native';

type EpicProps = {
  size?: number,
  duration?: number,
  color?: string,
  style?: ViewStyleProp
};

const EpicDefaultProps = {
  size: 200,
  color: 'red',
  duration: 4000 // Match original CSS's default 4000ms
};

export const FulfillingBouncingCircleSpinner = ({ size, color, duration, style, ...props }: EpicProps) => {
  const [animate] = useState(new Animated.Value(0));

  const spinnerStyle = StyleSheet.create({
    container: {
      height: size,
      width: size,
      position: 'relative'
    },
    circle: {
      height: size,
      width: size,
      borderRadius: size * 0.5,
      position: 'relative',
      borderWidth: size * 0.1
    },
    orbit: {
      height: size,
      width: size,
      position: 'absolute',
      top: 0,
      left: 0,
      borderColor: color,
      borderRadius: size * 0.5,
      borderWidth: size * 0.03
    }
  });

  // Container rotation (matches fulfilling-bouncing-circle-spinner-animation)
  const containerRotation = animate.interpolate({
    inputRange: [0, 1],
    outputRange: ['0deg', '360deg'],
    extrapolate: 'clamp'
  });

  // Orbit scale animation (matches fulfilling-bouncing-circle-spinner-orbit-animation)
  const orbitScale = animate.interpolate({
    inputRange: [0, 0.5, 0.625, 0.75, 0.875, 1],
    outputRange: [1, 1, 0.8, 1, 0.8, 1],
    extrapolate: 'clamp'
  });

  // Circle scale animation (matches the scale part of fulfilling-bouncing-circle-spinner-circle-animation)
  const circleScale = animate.interpolate({
    inputRange: [0, 0.5, 0.625, 0.75, 0.875, 1],
    outputRange: [1, 1, 1.4, 1, 1.4, 1],
    extrapolate: 'clamp'
  });

  // Circle border color animation (matches the border color changes in CSS keyframes)
  // React Native supports borderColor as an array [top, right, bottom, left]
  const circleBorderColor = animate.interpolate({
    inputRange: [0, 0.167, 0.334, 0.5, 1],
    outputRange: [
      `${color}, transparent, transparent, transparent`, // 0%: only top border visible
      `${color}, ${color}, transparent, transparent`, // 16.7%: top + right
      `${color}, ${color}, ${color}, transparent`, // 33.4%: top + right + bottom
      `${color}, ${color}, ${color}, ${color}`, // 50%: full border
      `${color}, transparent, transparent, transparent` // 100%: reset to only top
    ],
    extrapolate: 'clamp'
  });

  useEffect(() => {
    // Run a single timing animation from 0 to 1, loop infinitely
    // Uses Easing.ease to match CSS's default ease timing
    Animated.loop(
      Animated.timing(animate, {
        toValue: 1,
        duration: duration,
        useNativeDriver: false, // Required for borderColor interpolation (not supported by native driver)
        easing: Easing.ease
      })
    ).start();
  }, [animate, duration]);

  return (
    <Animated.View 
      style={[
        style, 
        spinnerStyle.container, 
        { transform: [{ rotate: containerRotation }] }
      ]} 
      {...props}
    >
      <Animated.View 
        style={[
          spinnerStyle.circle, 
          { 
            transform: [{ scale: circleScale }],
            borderColor: circleBorderColor
          }
        ]} 
      />
      <Animated.View 
        style={[
          spinnerStyle.orbit, 
          { transform: [{ scale: orbitScale }] }
        ]} 
      />
    </Animated.View>
  );
};

FulfillingBouncingCircleSpinner.defaultProps = EpicDefaultProps;

Key Changes & Explanations:

  • Orbit Animation: We map the CSS keyframe time points (0%, 50%, 62.5%, etc.) to inputRange values (0, 0.5, 0.625, etc.) and match the corresponding scale values in outputRange. This replicates the "wait half the duration, then bounce twice" behavior.
  • Circle Border Color: React Native lets you set borderColor as an array of four values (top, right, bottom, left), which we use to mimic CSS's ability to color individual borders. The interpolation steps match exactly when each border becomes visible in your original keyframes.
  • Circle Scale: Similar to the orbit, we map the CSS scale keyframes to interpolation ranges to get the bounce effect after the 50% mark.
  • Native Driver Note: We set useNativeDriver: false because borderColor interpolation isn't supported by the native driver. If you wanted to optimize, you could split the animation into two separate Animated values (one for native-compatible transforms, one for border styles), but this implementation keeps things simple and accurate.
  • Animation Loop: Instead of using Animated.sequence to go back to 0, we just loop a single timing animation from 0 to 1—this matches how CSS infinite keyframes work (they loop from start to end repeatedly).

This should give you an exact replica of your original React component's animation behavior in React Native!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:16:50