Skip to content

Commit 69c1602

Browse files
committed
Add better opacity and children handling tests and examples
1 parent 875d61b commit 69c1602

File tree

3 files changed

+112
-2
lines changed

3 files changed

+112
-2
lines changed

apps/common-app/src/apps/css/examples/animations/screens/animatedProperties/svg/RadialGradient.tsx

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Animated, {
55
// TODO: Fix me
66
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
77
// @ts-ignore RNSVG doesn't export types for web, see https://github.com/software-mansion/react-native-svg/pull/2801
8-
import { RadialGradient, Rect, Svg } from 'react-native-svg';
8+
import { RadialGradient, Rect, Stop, Svg } from 'react-native-svg';
99

1010
import { ExamplesScreen } from '@/apps/css/components';
1111

@@ -152,6 +152,26 @@ export default function RadialGradientExample() {
152152
},
153153
},
154154
},
155+
{
156+
title: 'Opacity calculation',
157+
description:
158+
"Stop's opacity is multiplied by color's opacity.",
159+
160+
keyframes: {
161+
from: {
162+
gradient: [
163+
{ offset: '0%', color: '#9b0808', opacity: 1 },
164+
{ offset: '100%', color: '#35e20a', opacity: 1 },
165+
],
166+
},
167+
to: {
168+
gradient: [
169+
{ offset: '0%', color: '#9b080800', opacity: 1 },
170+
{ offset: '100%', color: '#35e20a', opacity: 1 },
171+
],
172+
},
173+
},
174+
},
155175
],
156176
title: 'Opacity',
157177
},
@@ -443,6 +463,84 @@ export default function RadialGradientExample() {
443463
},
444464
],
445465
},
466+
{
467+
name: 'Stops as children and props',
468+
renderExample: ({ animation }) => (
469+
<Svg height={300} width={300}>
470+
<AnimatedGrad
471+
animatedProps={animation}
472+
gradientUnits="objectBoundingBox"
473+
id="radialGrad2">
474+
<Stop offset="0%" stopColor="red" stopOpacity="1" />
475+
<Stop offset="50%" stopColor="yellow" stopOpacity="1" />
476+
</AnimatedGrad>
477+
<Rect
478+
fill="url(#radialGrad2)"
479+
height={100}
480+
width={100}
481+
x={100}
482+
y={100}
483+
/>
484+
</Svg>
485+
),
486+
sections: [
487+
{
488+
examples: [
489+
{
490+
title: 'Children stops',
491+
description: `If no animation between stops is needed, the stops can be provided as children of the animated RadialGradient. \n\n${FOCAL_POINT_DISCLAIMER}`,
492+
keyframes: {
493+
from: {
494+
fx: '30%',
495+
fy: '30%',
496+
cx: '50%',
497+
cy: '50%',
498+
r: '30%',
499+
},
500+
to: {
501+
fx: '30%',
502+
fy: '30%',
503+
cx: '45%',
504+
cy: '45%',
505+
r: '60%',
506+
},
507+
},
508+
},
509+
{
510+
title: 'Prop stops',
511+
description: `If there is an animation between stops needed, the stops have to be provided as the gradient prop.`,
512+
keyframes: {
513+
from: {
514+
gradient: [
515+
{ offset: '0%', color: 'red' },
516+
{ offset: '50%', color: 'yellow' },
517+
],
518+
},
519+
to: {
520+
gradient: [
521+
{ offset: '0%', color: '#00ffff', opacity: 1 },
522+
{ offset: '100%', color: '#0000ff', opacity: 0.8 },
523+
],
524+
},
525+
},
526+
},
527+
{
528+
title: 'Mixed stops',
529+
description: `You cannot mix children stops and prop stops. It will result in prop stops being prioritized.`,
530+
keyframes: {
531+
to: {
532+
gradient: [
533+
{ offset: '0%', color: '#00ffff', opacity: 1 },
534+
{ offset: '100%', color: '#0000ff', opacity: 0.8 },
535+
],
536+
},
537+
},
538+
},
539+
],
540+
title: 'Stops as props or as children',
541+
},
542+
],
543+
},
446544
]}
447545
/>
448546
);

packages/react-native-reanimated/src/css/svg/native/processors/__tests__/stops.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ describe(processSVGGradientStops, () => {
3232
expect(result[0].color).toBe(2147483903);
3333
expect(result[0].offset).toBe(0.5);
3434
});
35+
36+
it('merges opacity into the color integer using bitwise operations', () => {
37+
const input = [{ offset: 0.5, color: '#0000ffee', opacity: 0.5 }];
38+
const result = processSVGGradientStops(input);
39+
40+
// Alpha: Math.round(0.5 * 238) = 119 (0x77)
41+
// Color: 0x0000ffee
42+
// Result: (0x77 << 24) | 0x0000ff = 0x770000ff (1996488959)
43+
expect(result[0].color).toBe(1996488959);
44+
expect(result[0].offset).toBe(0.5);
45+
});
3546
});
3647

3748
describe('Sorting and Offsets', () => {

packages/react-native-reanimated/src/css/svg/native/processors/stops.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ export const processSVGGradientStops = ((stops) => {
2424
stop.opacity !== undefined ? processPercentage(stop.opacity) : 1;
2525
const finalColor =
2626
typeof rawColor === 'number' && typeof stopOpacity === 'number'
27-
? ((Math.round(stopOpacity * 255) << 24) | (rawColor & 0x00ffffff)) >>>
27+
? ((Math.round(((rawColor >>> 24) & 0xff) * stopOpacity) << 24) |
28+
(rawColor & 0x00ffffff)) >>>
2829
0
2930
: rawColor;
3031
return {

0 commit comments

Comments
 (0)