Skip to content

Commit 7938aa9

Browse files
authored
feat: Implement CSS SVG Polygon (#9008)
## Summary This PR adds SVG Ploygon support in CSS animations/transitions. ## Test plan You can play with prepared examples: Run fabric-example -> CSS -> Animated Properties -> SVG Properties -> Polygon ## Example recording https://github.com/user-attachments/assets/3b03836f-7c3d-49e6-a70b-5ab62030f38e
1 parent 4657a97 commit 7938aa9

8 files changed

Lines changed: 199 additions & 13 deletions

File tree

apps/common-app/src/apps/css/examples/animations/routes/properties/svg.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export const svgPropertiesRoutes = {
4343
Component: svgAnimatedProperties.Path,
4444
name: 'Path',
4545
},
46+
Polygon: {
47+
Component: svgAnimatedProperties.Polygon,
48+
name: 'Polygon',
49+
},
4650
Polyline: {
4751
Component: svgAnimatedProperties.Polyline,
4852
name: 'Polyline',
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import Animated, { type CSSAnimationKeyframes } from 'react-native-reanimated';
2+
// TODO: Fix me
3+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
4+
// @ts-ignore RNSVG doesn't export types for web, see https://github.com/software-mansion/react-native-svg/pull/2801
5+
import { Polygon, type PolygonProps, Svg } from 'react-native-svg';
6+
7+
import { ExamplesScreen } from '@/apps/css/components';
8+
import { colors } from '@/theme';
9+
10+
const AnimatedPolygon = Animated.createAnimatedComponent(Polygon);
11+
12+
export default function PolygonExample() {
13+
return (
14+
<ExamplesScreen<
15+
{ keyframes: CSSAnimationKeyframes<PolygonProps> },
16+
PolygonProps
17+
>
18+
buildAnimation={({ keyframes }) => ({
19+
animationDirection: 'alternate',
20+
animationDuration: '1s',
21+
animationIterationCount: 'infinite',
22+
animationName: keyframes,
23+
animationTimingFunction: 'ease-in-out',
24+
})}
25+
renderExample={({ animation }) => (
26+
<Svg height={100} viewBox="0 0 100 100" width={100}>
27+
<AnimatedPolygon
28+
animatedProps={animation}
29+
fill={colors.primaryLight}
30+
points="50,10 90,90 10,90"
31+
stroke={colors.primary}
32+
strokeLinejoin="round"
33+
strokeWidth={3}
34+
/>
35+
</Svg>
36+
)}
37+
tabs={[
38+
{
39+
name: 'Points',
40+
sections: [
41+
{
42+
examples: [
43+
{
44+
description:
45+
'Smooth interpolation between two triangle shapes with the same number of points',
46+
keyframes: {
47+
from: {
48+
points: '50,10 90,90 10,90',
49+
},
50+
to: {
51+
points: '50,90 90,10 10,10',
52+
},
53+
},
54+
title: 'Triangle flip',
55+
},
56+
{
57+
description:
58+
'Smooth animation between two diamond orientations',
59+
keyframes: {
60+
from: {
61+
points: '50,10 90,50 50,90 10,50',
62+
},
63+
to: {
64+
points: '30,20 80,20 70,80 20,80',
65+
},
66+
},
67+
title: 'Diamond morph',
68+
},
69+
{
70+
description: 'Pentagon morphs between two rotational states',
71+
keyframes: {
72+
from: {
73+
points: '50,5 95,35 80,90 20,90 5,35',
74+
},
75+
to: {
76+
points: '50,95 5,65 20,10 80,10 95,65',
77+
},
78+
},
79+
title: 'Pentagon rotation',
80+
},
81+
{
82+
description:
83+
'Hexagon smoothly morphs between a tall and wide shape',
84+
keyframes: {
85+
from: {
86+
points: '50,5 85,25 85,75 50,95 15,75 15,25',
87+
},
88+
to: {
89+
points: '50,20 90,35 90,65 50,80 10,65 10,35',
90+
},
91+
},
92+
title: 'Hexagon breathe',
93+
},
94+
],
95+
title: 'Same Number of Points',
96+
},
97+
{
98+
examples: [
99+
{
100+
description:
101+
'Smooth interpolation between shapes with different numbers of points (triangle to diamond)',
102+
keyframes: {
103+
from: {
104+
points: '50,10 90,90 10,90',
105+
},
106+
to: {
107+
points: '50,10 90,50 50,90 10,50',
108+
},
109+
},
110+
title: 'Triangle to diamond',
111+
},
112+
{
113+
description:
114+
'Smooth interpolation when reducing the number of points (pentagon to triangle)',
115+
keyframes: {
116+
from: {
117+
points: '50,5 95,35 80,90 20,90 5,35',
118+
},
119+
to: {
120+
points: '50,10 90,90 10,90',
121+
},
122+
},
123+
title: 'Pentagon to triangle',
124+
},
125+
],
126+
title: 'Different Number of Points',
127+
},
128+
],
129+
},
130+
{
131+
name: 'Appearance',
132+
sections: [
133+
{
134+
examples: [
135+
{
136+
keyframes: {
137+
to: {
138+
opacity: 0,
139+
},
140+
},
141+
title: 'Opacity',
142+
},
143+
],
144+
title: 'Opacity',
145+
},
146+
],
147+
},
148+
]}
149+
/>
150+
);
151+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export default function PolylineExample() {
100100
examples: [
101101
{
102102
description:
103-
'Interpolation between different numbers of points is **not supported**, so the points change **abruptly**',
103+
'Smooth interpolation between shapes with different numbers of points',
104104
keyframes: {
105105
from: {
106106
points: '10,80 50,20 90,80',
@@ -113,7 +113,7 @@ export default function PolylineExample() {
113113
},
114114
{
115115
description:
116-
'Reducing the number of points also causes an **abrupt** change instead of a smooth transition',
116+
'Smooth interpolation when reducing the number of points',
117117
keyframes: {
118118
from: {
119119
points: '10,50 25,20 40,80 55,20 70,80 85,20 90,50',

apps/common-app/src/apps/css/examples/animations/screens/animatedProperties/svg/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Image from './Image';
66
import Line from './Line';
77
import LinearGradient from './LinearGradient';
88
import Path from './Path';
9+
import Polygon from './Polygon';
910
import Polyline from './Polyline';
1011
import RadialGradient from './RadialGradient';
1112
import Rect from './Rect';
@@ -19,6 +20,7 @@ export default {
1920
Line,
2021
LinearGradient,
2122
Path,
23+
Polygon,
2224
Polyline,
2325
RadialGradient,
2426
Rect,

packages/react-native-reanimated/src/css/svg/init.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
SVG_LINE_PROPERTIES_CONFIG,
1212
SVG_LINEAR_GRADIENT_PROPERTIES_CONFIG,
1313
SVG_PATH_PROPERTIES_CONFIG,
14+
SVG_POLYGON_PROPERTIES_CONFIG,
1415
SVG_POLYLINE_PROPERTIES_CONFIG,
1516
SVG_RADIAL_GRADIENT_PROPERTIES_CONFIG,
1617
SVG_RECT_PROPERTIES_CONFIG,
@@ -30,6 +31,10 @@ export function initSvgCssSupport() {
3031
SVG_RADIAL_GRADIENT_PROPERTIES_CONFIG
3132
);
3233
registerComponentPropsBuilder('RNSVGPath', SVG_PATH_PROPERTIES_CONFIG);
34+
registerComponentPropsBuilder(
35+
getCompoundComponentName('RNSVGPath', 'Polygon'),
36+
SVG_POLYGON_PROPERTIES_CONFIG
37+
);
3338
registerComponentPropsBuilder(
3439
getCompoundComponentName('RNSVGPath', 'Polyline'),
3540
SVG_POLYLINE_PROPERTIES_CONFIG

packages/react-native-reanimated/src/css/svg/native/configs/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export * from './image';
66
export * from './line';
77
export * from './linearGradient';
88
export * from './path';
9+
export * from './polygon';
910
export * from './polyline';
1011
export * from './radialGradient';
1112
export * from './rect';
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use strict';
2+
3+
// TODO: Fix me
4+
// @ts-ignore RNSVG doesn't export types for web, see https://github.com/software-mansion/react-native-svg/pull/2801
5+
import type { PolygonProps } from 'react-native-svg';
6+
7+
import { processPolygonPoints } from '../processors';
8+
import type { SvgStyleBuilderConfig } from './common';
9+
import { SVG_COMMON_PROPERTIES_CONFIG } from './common';
10+
11+
// TODO: Fix me
12+
// @ts-ignore RNSVG doesn't export types for web, see https://github.com/software-mansion/react-native-svg/pull/2801
13+
export const SVG_POLYGON_PROPERTIES_CONFIG: SvgStyleBuilderConfig<PolygonProps> =
14+
{
15+
...SVG_COMMON_PROPERTIES_CONFIG,
16+
points: { process: processPolygonPoints },
17+
};

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

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,23 @@ import type { NumberProp } from 'react-native-svg';
44
import type { ValueProcessor } from '../../../../common';
55
import { processSVGPath } from './path';
66

7+
function extractPolyPoints(points: string | ReadonlyArray<NumberProp>): string {
8+
const polyPoints = Array.isArray(points) ? points.join(',') : points;
9+
10+
return (polyPoints as string)
11+
.replace(/([^eE])-/g, '$1 -')
12+
.split(/(?:\s+|\s*,\s*)/g)
13+
.join(' ');
14+
}
15+
716
export const processPolylinePoints: ValueProcessor<
817
string | ReadonlyArray<NumberProp>
9-
> = (points) => {
10-
const polyPoints = Array.isArray(points) ? points.join(',') : points;
18+
> = (points) => ({
19+
d: processSVGPath(`M${extractPolyPoints(points)}`),
20+
});
1121

12-
return {
13-
d: processSVGPath(
14-
`M${(polyPoints as string)
15-
.replace(/([^eE])-/g, '$1 -')
16-
.split(/(?:\s+|\s*,\s*)/g)
17-
.join(' ')}`
18-
),
19-
};
20-
};
22+
export const processPolygonPoints: ValueProcessor<
23+
string | ReadonlyArray<NumberProp>
24+
> = (points) => ({
25+
d: processSVGPath(`M${extractPolyPoints(points)}z`),
26+
});

0 commit comments

Comments
 (0)