Skip to content

Commit a95137a

Browse files
Enable support for SVGText in CSS (#9022)
## Summary Enable support for [SVGText](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/text). ## Test plan I added examples in the fabric-example: CSS -> SVG Examples -> Text --------- Co-authored-by: Mateusz Łopaciński <lop.mateusz.2001@gmail.com>
1 parent 01131f4 commit a95137a

14 files changed

Lines changed: 541 additions & 3 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
@@ -46,6 +46,10 @@ export const svgPropertiesRoutes = {
4646
Component: svgAnimatedProperties.Rect,
4747
name: 'Rect',
4848
},
49+
Text: {
50+
Component: svgAnimatedProperties.Text,
51+
name: 'Text',
52+
},
4953
Common: {
5054
name: 'Common',
5155
routes: {
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
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 { Svg, Text, type TextProps } from 'react-native-svg';
6+
7+
import { ExamplesScreen } from '@/apps/css/components';
8+
import { colors } from '@/theme';
9+
10+
const AnimatedText = Animated.createAnimatedComponent(Text);
11+
12+
export default function TextExample() {
13+
return (
14+
<ExamplesScreen<{ keyframes: CSSAnimationKeyframes<TextProps> }, TextProps>
15+
buildAnimation={({ keyframes }) => ({
16+
animationDirection: 'alternate',
17+
animationDuration: '1s',
18+
animationIterationCount: 'infinite',
19+
animationName: keyframes,
20+
animationTimingFunction: 'linear',
21+
})}
22+
// TODO:
23+
// Check why adding animation of props
24+
// like 'fontSize' works only if
25+
// prop not used as an inline prop (so in case of'fontSize' line 31 commented out)
26+
renderExample={({ animation }) => (
27+
<Svg height={60} width={200}>
28+
<AnimatedText
29+
animatedProps={animation}
30+
fill={colors.primary}
31+
fontSize={20}
32+
x={30}
33+
y={40}>
34+
Hello SVG
35+
</AnimatedText>
36+
</Svg>
37+
)}
38+
tabs={[
39+
{
40+
name: 'Position',
41+
sections: [
42+
{
43+
examples: [
44+
{
45+
description:
46+
'Animates x position from 30 to 150 using absolute values',
47+
keyframes: {
48+
from: {
49+
x: 30,
50+
},
51+
to: {
52+
x: 150,
53+
},
54+
},
55+
title: 'Absolute value',
56+
},
57+
{
58+
description:
59+
'Animates x position from 30 to 150 using absolute values',
60+
keyframes: {
61+
from: {
62+
x: 30,
63+
},
64+
to: {
65+
x: [30, 100],
66+
},
67+
},
68+
title: 'Absolute value',
69+
},
70+
{
71+
description:
72+
'Animation using only percentage values for smooth relative positioning',
73+
keyframes: {
74+
from: {
75+
x: '5%',
76+
},
77+
to: {
78+
x: '75%',
79+
},
80+
},
81+
title: 'Percentage values (from 5% to 75%)',
82+
},
83+
{
84+
description:
85+
'Smoothly interpolates between an absolute and a percentage value by resolving them to the same unit',
86+
keyframes: {
87+
from: {
88+
x: 30,
89+
},
90+
to: {
91+
x: '75%',
92+
},
93+
},
94+
title: 'Mixed values (from 30 to 75%)',
95+
},
96+
],
97+
title: 'X Position',
98+
},
99+
{
100+
examples: [
101+
{
102+
description:
103+
'Animates y position from 40 to 15 using absolute values',
104+
keyframes: {
105+
from: {
106+
y: 40,
107+
},
108+
to: {
109+
y: 15,
110+
},
111+
},
112+
title: 'Absolute value',
113+
},
114+
{
115+
description:
116+
'Animation using only percentage values for smooth relative positioning',
117+
keyframes: {
118+
from: {
119+
y: '30%',
120+
},
121+
to: {
122+
y: '80%',
123+
},
124+
},
125+
title: 'Percentage values (from 30% to 80%)',
126+
},
127+
{
128+
description:
129+
'Smoothly interpolates between an absolute and a percentage value by resolving them to the same unit',
130+
keyframes: {
131+
from: {
132+
y: 20,
133+
},
134+
to: {
135+
y: '80%',
136+
},
137+
},
138+
title: 'Mixed values (from 20 to 80%)',
139+
},
140+
],
141+
title: 'Y Position',
142+
},
143+
{
144+
examples: [
145+
{
146+
description:
147+
'Shifts the whole text horizontally by animating a single dx offset',
148+
keyframes: {
149+
to: {
150+
dx: 30,
151+
},
152+
},
153+
title: 'Absolute value',
154+
},
155+
{
156+
description:
157+
'Animates per-glyph horizontal offsets to spread the letters apart',
158+
keyframes: {
159+
to: {
160+
dx: [0, 3, 6, 9, 12, 9, 6, 3, 0],
161+
},
162+
},
163+
title: 'Per-glyph spread',
164+
},
165+
{
166+
description:
167+
'Animates per-glyph horizontal offsets to spread the letters apart',
168+
keyframes: {
169+
to: {
170+
dx: [0, 100],
171+
},
172+
},
173+
title: 'Per-glyph spread',
174+
},
175+
],
176+
title: 'DX (Horizontal Offset)',
177+
},
178+
{
179+
examples: [
180+
{
181+
description:
182+
'Shifts the whole text vertically by animating a single dy offset',
183+
keyframes: {
184+
to: {
185+
dy: 15,
186+
},
187+
},
188+
title: 'Absolute value',
189+
},
190+
{
191+
description:
192+
'Animates per-glyph vertical offsets to create a wave effect',
193+
keyframes: {
194+
to: {
195+
dy: [0, -8, -12, -8, 0, 8, 12, 8, 0],
196+
},
197+
},
198+
title: 'Per-glyph wave',
199+
},
200+
],
201+
title: 'DY (Vertical Offset)',
202+
},
203+
],
204+
},
205+
{
206+
name: 'Rotation',
207+
sections: [
208+
{
209+
examples: [
210+
{
211+
description: 'Rotates the entire text from 0° to 30°',
212+
keyframes: {
213+
to: {
214+
rotate: 30,
215+
},
216+
},
217+
title: 'Absolute value',
218+
},
219+
{
220+
description:
221+
'Animates per-glyph rotation to create a cascading tilt effect',
222+
keyframes: {
223+
to: {
224+
rotate: [0, 5, 10, 20, 30, 20, 10, 5, 0],
225+
},
226+
},
227+
title: 'Per-glyph cascade',
228+
},
229+
],
230+
title: 'Rotation',
231+
},
232+
],
233+
},
234+
{
235+
name: 'Appearance',
236+
sections: [
237+
{
238+
examples: [
239+
{
240+
keyframes: {
241+
to: {
242+
opacity: 0,
243+
},
244+
},
245+
title: 'Opacity',
246+
},
247+
],
248+
title: 'Opacity',
249+
},
250+
{
251+
examples: [
252+
{
253+
keyframes: {
254+
to: {
255+
fill: 'red',
256+
},
257+
},
258+
title: 'Fill color',
259+
},
260+
{
261+
keyframes: {
262+
to: {
263+
fillOpacity: 0.2,
264+
},
265+
},
266+
title: 'Fill opacity',
267+
},
268+
],
269+
title: 'Fill',
270+
},
271+
],
272+
},
273+
]}
274+
/>
275+
);
276+
}

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
@@ -10,6 +10,7 @@ import Polygon from './Polygon';
1010
import Polyline from './Polyline';
1111
import RadialGradient from './RadialGradient';
1212
import Rect from './Rect';
13+
import Text from './Text';
1314

1415
export default {
1516
Circle,
@@ -24,4 +25,5 @@ export default {
2425
Polyline,
2526
RadialGradient,
2627
Rect,
28+
Text,
2729
};

apps/fabric-example/ios/Podfile.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-native-reanimated/Common/cpp/reanimated/CSS/InterpolatorRegistry.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <reanimated/CSS/common/transforms/TransformMatrix2D.h>
1414
#include <reanimated/CSS/common/values/complex/CSSBoxShadow.h>
1515

16+
#include <reanimated/CSS/svg/values/CSSLengthArray.h>
1617
#include <reanimated/CSS/svg/values/SVGBrush.h>
1718
#include <reanimated/CSS/svg/values/SVGPath.h>
1819
#include <reanimated/CSS/svg/values/SVGStops.h>
@@ -367,6 +368,19 @@ const InterpolatorFactoriesRecord SVG_RADIAL_GRADIENT_INTERPOLATORS = mergeInter
367368
// {"gradientTransform", value<CSSKeyword>("")},
368369
}});
369370

371+
const InterpolatorFactoriesRecord SVG_TEXT_INTERPOLATORS = mergeInterpolators(
372+
{SVG_COMMON_INTERPOLATORS,
373+
InterpolatorFactoriesRecord{
374+
{"x", value<CSSLengthArray>(CSSLengthArray(), {RelativeTo::Parent, "width"})},
375+
{"y", value<CSSLengthArray>(CSSLengthArray(), {RelativeTo::Parent, "height"})},
376+
{"dx", value<CSSLengthArray>(CSSLengthArray(), {RelativeTo::Parent, "width"})},
377+
{"dy", value<CSSLengthArray>(CSSLengthArray(), {RelativeTo::Parent, "height"})},
378+
{"rotate", value<CSSLengthArray>(CSSLengthArray(), {RelativeTo::Parent, "width"})},
379+
// TODO: Implement when supported by RNSVG
380+
// {"textLength", value<CSSLength, CSSKeyword>(0, {RelativeTo::Parent, "width"})},
381+
// {"lengthAdjust", value<CSSKeyword>("spacing")},
382+
}});
383+
370384
// ==================
371385
// COMPONENT REGISTRY
372386
// ==================
@@ -386,6 +400,7 @@ ComponentInterpolatorsMap initializeRegistry() {
386400
registry["RNSVGPath"] = SVG_PATH_INTERPOLATORS;
387401
registry["RNSVGRect"] = SVG_RECT_INTERPOLATORS;
388402
registry["RNSVGRadialGradient"] = SVG_RADIAL_GRADIENT_INTERPOLATORS;
403+
registry["RNSVGText"] = SVG_TEXT_INTERPOLATORS;
389404
}
390405

391406
return registry;

packages/react-native-reanimated/Common/cpp/reanimated/CSS/common/values/CSSValueVariant.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <reanimated/CSS/common/values/CSSNumber.h>
88
#include <reanimated/CSS/common/values/CSSValueVariant.h>
99
#include <reanimated/CSS/common/values/complex/CSSBoxShadow.h>
10+
#include <reanimated/CSS/svg/values/CSSLengthArray.h>
1011
#include <reanimated/CSS/svg/values/SVGBrush.h>
1112
#include <reanimated/CSS/svg/values/SVGPath.h>
1213
#include <reanimated/CSS/svg/values/SVGStops.h>
@@ -166,6 +167,7 @@ template class CSSValueVariant<CSSBoxShadow>;
166167
template class CSSValueVariant<CSSDiscreteArray<CSSKeyword>>;
167168

168169
template class CSSValueVariant<SVGPath>;
170+
template class CSSValueVariant<CSSLengthArray>;
169171
template class CSSValueVariant<SVGStops>;
170172
template class CSSValueVariant<SVGStrokeDashArray, CSSKeyword>;
171173
template class CSSValueVariant<SVGBrush>;

packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ResolvableValueInterpolator.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <reanimated/CSS/common/values/CSSKeyword.h>
88
#include <reanimated/CSS/common/values/CSSLength.h>
99
#include <reanimated/CSS/common/values/CSSNumber.h>
10+
#include <reanimated/CSS/svg/values/CSSLengthArray.h>
1011
#include <reanimated/CSS/svg/values/SVGStrokeDashArray.h>
1112

1213
#include <memory>
@@ -44,6 +45,7 @@ folly::dynamic ResolvableValueInterpolator<AllowedTypes...>::interpolateValue(
4445

4546
template class ResolvableValueInterpolator<CSSLength>;
4647
template class ResolvableValueInterpolator<CSSLength, CSSKeyword>;
48+
template class ResolvableValueInterpolator<CSSLengthArray>;
4749
template class ResolvableValueInterpolator<SVGStrokeDashArray, CSSKeyword>;
4850

4951
} // namespace reanimated::css

0 commit comments

Comments
 (0)