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
inputRangevalues (0, 0.5, 0.625, etc.) and match the corresponding scale values inoutputRange. This replicates the "wait half the duration, then bounce twice" behavior. - Circle Border Color: React Native lets you set
borderColoras 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: falsebecauseborderColorinterpolation 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.sequenceto 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
相关产品推荐
相关产品推荐

