From ff2ca9269d8875159899786b994b9670aae08d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrycja=20Kali=C5=84ska?= <59940332+patrycjakalinska@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:08:26 +0100 Subject: [PATCH] fix: withSpring color properties flickering (#6821) ## Summary This PR addresses an issue where color-based properties (`backgroundColor`, `boxShadow`) were causing flickering when used with withSpring animations. The root cause of the flickering was RGBA values going below `0`, resulting in `NaN` values. I introduced `clampRGBA` function that guards values within given limits. Before: https://github.com/user-attachments/assets/ace128a8-3f4b-41be-98c6-d34339808283 After: https://github.com/user-attachments/assets/b29f1248-2385-48c2-8fad-e261ee21b46a ## Test plan Paste this code to EmptyExample - it should work on both Paper and Fabric:
EmptyExample code ```js import { Text, StyleSheet, View, Pressable } from 'react-native'; import React from 'react'; import Animated, { useSharedValue, withSpring, useAnimatedStyle, } from 'react-native-reanimated'; export default function EmptyExample() { const pressed = useSharedValue(false); const animatedStyle = useAnimatedStyle(() => { return { backgroundColor: withSpring(pressed.value ? 'blue' : 'red'), }; }); return ( { pressed.value = !pressed.value; }}> Press me ); } const styles = StyleSheet.create({ box: { width: 50, height: 50, }, }); ```
--- packages/react-native-reanimated/src/Colors.ts | 7 +++++++ packages/react-native-reanimated/src/animation/util.ts | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/packages/react-native-reanimated/src/Colors.ts b/packages/react-native-reanimated/src/Colors.ts index 40cd3ecf0bae..c0d835eb3f64 100644 --- a/packages/react-native-reanimated/src/Colors.ts +++ b/packages/react-native-reanimated/src/Colors.ts @@ -164,6 +164,13 @@ function parsePercentage(str: string): number { return int / 100; } +export function clampRGBA(RGBA: ParsedColorArray): void { + 'worklet'; + for (let i = 0; i < 4; i++) { + RGBA[i] = Math.max(0, Math.min(RGBA[i], 1)); + } +} + const names: Record = makeShareable({ transparent: 0x00000000, diff --git a/packages/react-native-reanimated/src/animation/util.ts b/packages/react-native-reanimated/src/animation/util.ts index 73cb013e3d0b..b0d838b2ce6c 100644 --- a/packages/react-native-reanimated/src/animation/util.ts +++ b/packages/react-native-reanimated/src/animation/util.ts @@ -8,6 +8,7 @@ import { rgbaArrayToRGBAColor, toGammaSpace, toLinearSpace, + clampRGBA, } from '../Colors'; import { ReduceMotion, isWorkletFunction } from '../commonTypes'; import type { @@ -263,6 +264,9 @@ function decorateAnimation( res.push(animation[i].current); }); + // We need to clamp the res values to make sure they are in the correct RGBA range + clampRGBA(res as ParsedColorArray); + animation.current = rgbaArrayToRGBAColor( toGammaSpace(res as ParsedColorArray) ); @@ -283,6 +287,9 @@ function decorateAnimation( res.push(animation[i].current); }); + // We need to clamp the res values to make sure they are in the correct RGBA range + clampRGBA(res as ParsedColorArray); + animation.current = rgbaArrayToRGBAColor( toGammaSpace(res as ParsedColorArray) );