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)
);