Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export const svgPropertiesRoutes = {
Component: svgAnimatedProperties.Polyline,
name: 'Polyline',
},
Pattern: {
Component: svgAnimatedProperties.Pattern,
name: 'Pattern',
},
LinearGradient: {
Component: svgAnimatedProperties.LinearGradient,
name: 'LinearGradient',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import type { PropsWithChildren } from 'react';
import Animated, { type CSSAnimationKeyframes } from 'react-native-reanimated';
// TODO: Fix me
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore RNSVG doesn't export types for web, see https://github.com/software-mansion/react-native-svg/pull/2801
import type { PatternProps } from 'react-native-svg';
import { Defs, Pattern, Rect, Svg } from 'react-native-svg';

import { ExamplesScreen } from '@/apps/css/components';
import { colors } from '@/theme';

const TypedDefs = Defs as React.ComponentType<PropsWithChildren>;
const AnimatedPattern = Animated.createAnimatedComponent(Pattern);

type PatternExtraProps = {
patternUnits?: 'userSpaceOnUse' | 'objectBoundingBox';
tileWidth?: number;
tileHeight?: number;
};

// With userSpaceOnUse, numeric values are in viewBox user units, but percentages resolve
// relative to the SVG element's physical layout size in screen pixels. These two coordinate
// spaces only align when the SVG element's physical size matches the viewBox dimensions.
const MIXED_UNITS_NOTE =
"**Note:** With `userSpaceOnUse`, mixing absolute and percentage values is not fully supported — the absolute value is in viewBox user units, but the percentage resolves relative to the SVG element's physical layout size in screen pixels. These coordinate spaces only align when the two happen to match.";

export default function PatternExample() {
return (
<ExamplesScreen<
{
keyframes: CSSAnimationKeyframes<PatternProps>;
props?: PatternExtraProps;
},
PatternProps
>
buildAnimation={({ keyframes }) => ({
animationDirection: 'alternate',
animationDuration: '2s',
animationIterationCount: 'infinite',
animationName: keyframes,
animationTimingFunction: 'ease-in-out',
})}
renderExample={({ animation, props }) => (
<Svg height={100} viewBox="0 0 100 100" width={100}>
<TypedDefs>
<AnimatedPattern
animatedProps={animation}
height={props?.tileHeight ?? 20}
id="stripes"
patternUnits={props?.patternUnits ?? 'userSpaceOnUse'}
width={props?.tileWidth ?? 20}>
<Rect fill={colors.primary} height={10} width={20} />
<Rect fill={colors.primaryLight} height={10} width={20} y={10} />
</AnimatedPattern>
</TypedDefs>
<Rect fill="url(#stripes)" height={100} width={100} />
</Svg>
)}
tabs={[
{
name: 'Position',
sections: [
{
description:
'**x** property does not work on **Android** (not only in animations but also in regular inline styles)',
examples: [
{
description:
'With `userSpaceOnUse`, x is in the viewBox coordinate space. Animates from 5 to 20 viewBox user units.',
keyframes: { from: { x: 5 }, to: { x: 20 } },
props: {
patternUnits: 'userSpaceOnUse',
tileHeight: 20,
tileWidth: 20,
},
title: 'Numeric (userSpaceOnUse)',
},
{
description:
"With `objectBoundingBox`, x is a fraction of the bounding box of the element referencing the pattern. `0.05` = 5% and `0.2` = 20% of the referencing element's width.",
keyframes: { from: { x: 0.05 }, to: { x: 0.2 } },
props: {
patternUnits: 'objectBoundingBox',
tileHeight: 0.2,
tileWidth: 0.2,
},
title: 'Numeric (objectBoundingBox)',
},
{
description:
"Percentage values resolve relative to the SVG element's physical layout width in screen pixels.",
keyframes: { from: { x: '5%' }, to: { x: '20%' } },
title: 'Percentage',
},
{
description: MIXED_UNITS_NOTE,
keyframes: { from: { x: 20 }, to: { x: '5%' } },
title: 'Mixed',
},
],
labelTypes: ['iOS'],
title: 'X Offset',
},
{
description:
'**y** property does not work on **Android** (not only in animations but also in regular inline styles)',
examples: [
{
description:
'With `userSpaceOnUse`, y is in the viewBox coordinate space. Animates from 5 to 20 viewBox user units.',
keyframes: { from: { y: 5 }, to: { y: 20 } },
props: {
patternUnits: 'userSpaceOnUse',
tileHeight: 20,
tileWidth: 20,
},
title: 'Numeric (userSpaceOnUse)',
},
{
description:
"With `objectBoundingBox`, y is a fraction of the bounding box of the element referencing the pattern. `0.05` = 5% and `0.2` = 20% of the referencing element's height.",
keyframes: { from: { y: 0.05 }, to: { y: 0.2 } },
props: {
patternUnits: 'objectBoundingBox',
tileHeight: 0.2,
tileWidth: 0.2,
},
title: 'Numeric (objectBoundingBox)',
},
{
description:
"Percentage values resolve relative to the SVG element's physical layout height in screen pixels.",
keyframes: { from: { y: '5%' }, to: { y: '20%' } },
title: 'Percentage',
},
{
description: MIXED_UNITS_NOTE,
keyframes: { from: { y: 20 }, to: { y: '5%' } },
title: 'Mixed',
},
],
labelTypes: ['iOS'],
title: 'Y Offset',
},
],
},
{
name: 'Size',
sections: [
{
examples: [
{
description:
'With `userSpaceOnUse`, tile width is in the viewBox coordinate space. Animates from 15 to 30 viewBox user units.',
keyframes: { from: { width: 15 }, to: { width: 30 } },
props: { patternUnits: 'userSpaceOnUse' },
title: 'Numeric (userSpaceOnUse)',
},
{
description:
"With `objectBoundingBox`, tile width is a fraction of the bounding box of the element referencing the pattern. `0.15` = 15% and `0.3` = 30% of the referencing element's width.",
keyframes: { from: { width: 0.15 }, to: { width: 0.3 } },
props: { patternUnits: 'objectBoundingBox' },
title: 'Numeric (objectBoundingBox)',
},
{
description:
"Percentage values resolve relative to the SVG element's physical layout width in screen pixels.",
keyframes: {
from: { width: '15%' },
to: { width: '30%' },
},
title: 'Percentage',
},
{
description: MIXED_UNITS_NOTE,
keyframes: { from: { width: 15 }, to: { width: '30%' } },
title: 'Mixed',
},
],
title: 'Tile Width',
},
{
examples: [
{
description:
'With `userSpaceOnUse`, tile height is in the viewBox coordinate space. Animates from 15 to 30 viewBox user units.',
keyframes: { from: { height: 15 }, to: { height: 30 } },
props: { patternUnits: 'userSpaceOnUse' },
title: 'Numeric (userSpaceOnUse)',
},
{
description:
"With `objectBoundingBox`, tile height is a fraction of the bounding box of the element referencing the pattern. `0.15` = 15% and `0.3` = 30% of the referencing element's height.",
keyframes: { from: { height: 0.15 }, to: { height: 0.3 } },
props: { patternUnits: 'objectBoundingBox' },
title: 'Numeric (objectBoundingBox)',
},
{
description:
"Percentage values resolve relative to the SVG element's physical layout height in screen pixels.",
keyframes: {
from: { height: '15%' },
to: { height: '30%' },
},
title: 'Percentage',
},
{
description: MIXED_UNITS_NOTE,
keyframes: { from: { height: 15 }, to: { height: '30%' } },
title: 'Mixed',
},
],
title: 'Tile Height',
},
],
},
{
name: 'Units',
sections: [
{
examples: [
{
description:
'With `patternUnits: "userSpaceOnUse"`, the pattern tile is defined in absolute user coordinates. Animating to `"objectBoundingBox"` reinterprets those same values as fractions of the bounding box, causing an abrupt jump.',
keyframes: {
from: {
height: 20,
patternUnits: 'userSpaceOnUse',
width: 20,
},
to: {
height: 20,
patternUnits: 'objectBoundingBox',
width: 20,
},
},
title: 'patternUnits interpolation',
},
],
title: 'Coordinate systems',
},
],
},
]}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Image from './Image';
import Line from './Line';
import LinearGradient from './LinearGradient';
import Path from './Path';
import Pattern from './Pattern';
import Polygon from './Polygon';
import Polyline from './Polyline';
import RadialGradient from './RadialGradient';
Expand All @@ -21,6 +22,7 @@ export default {
Line,
LinearGradient,
Path,
Pattern,
Polygon,
Polyline,
RadialGradient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,17 @@ const InterpolatorFactoriesRecord SVG_PATH_INTERPOLATORS = mergeInterpolators(
{"d", value<SVGPath>("")},
}});

const InterpolatorFactoriesRecord SVG_PATTERN_INTERPOLATORS = mergeInterpolators(
{SVG_COMMON_INTERPOLATORS,
InterpolatorFactoriesRecord{
{"x", value<CSSLength>(0, {RelativeTo::Parent, "width"})},
{"y", value<CSSLength>(0, {RelativeTo::Parent, "height"})},
{"width", value<CSSLength>(0, {RelativeTo::Parent, "width"})},
{"height", value<CSSLength>(0, {RelativeTo::Parent, "height"})},
{"patternUnits", value<CSSIndex>(0)},
{"patternContentUnits", value<CSSIndex>(0)},
}});

const InterpolatorFactoriesRecord SVG_RADIAL_GRADIENT_INTERPOLATORS = mergeInterpolators(
{SVG_COMMON_INTERPOLATORS,
InterpolatorFactoriesRecord{
Expand Down Expand Up @@ -398,6 +409,7 @@ ComponentInterpolatorsMap initializeRegistry() {
registry["RNSVGLine"] = SVG_LINE_INTERPOLATORS;
registry["RNSVGLinearGradient"] = SVG_LINEAR_GRADIENT_INTERPOLATORS;
registry["RNSVGPath"] = SVG_PATH_INTERPOLATORS;
registry["RNSVGPattern"] = SVG_PATTERN_INTERPOLATORS;
registry["RNSVGRect"] = SVG_RECT_INTERPOLATORS;
registry["RNSVGRadialGradient"] = SVG_RADIAL_GRADIENT_INTERPOLATORS;
registry["RNSVGText"] = SVG_TEXT_INTERPOLATORS;
Expand Down
2 changes: 2 additions & 0 deletions packages/react-native-reanimated/src/css/svg/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
SVG_LINE_PROPERTIES_CONFIG,
SVG_LINEAR_GRADIENT_PROPERTIES_CONFIG,
SVG_PATH_PROPERTIES_CONFIG,
SVG_PATTERN_PROPERTIES_CONFIG,
SVG_POLYGON_PROPERTIES_CONFIG,
SVG_POLYLINE_PROPERTIES_CONFIG,
SVG_RADIAL_GRADIENT_PROPERTIES_CONFIG,
Expand All @@ -32,6 +33,7 @@ export function initSvgCssSupport() {
SVG_RADIAL_GRADIENT_PROPERTIES_CONFIG
);
registerComponentPropsBuilder('RNSVGPath', SVG_PATH_PROPERTIES_CONFIG);
registerComponentPropsBuilder('RNSVGPattern', SVG_PATTERN_PROPERTIES_CONFIG);
registerComponentPropsBuilder(
getCompoundComponentName('RNSVGPath', 'Polygon'),
SVG_POLYGON_PROPERTIES_CONFIG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './image';
export * from './line';
export * from './linearGradient';
export * from './path';
export * from './pattern';
export * from './polygon';
export * from './polyline';
export * from './radialGradient';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

import type { PatternProps } from 'react-native-svg';

// TODO: Fix me
// @ts-ignore RNSVG doesn't export types for web, see https://github.com/software-mansion/react-native-svg/pull/2801
import type { SvgStyleBuilderConfig } from './common';

// TODO: Fix me
// @ts-ignore RNSVG doesn't export types for web, see https://github.com/software-mansion/react-native-svg/pull/2801
export const SVG_PATTERN_PROPERTIES_CONFIG: SvgStyleBuilderConfig<PatternProps> =
{
x: true,
y: true,
width: true,
height: true,
patternUnits: {
process: (patternUnits) => (patternUnits === 'userSpaceOnUse' ? 1 : 0),
},
patternContentUnits: {
process: (patternContentUnits) =>
patternContentUnits === 'userSpaceOnUse' ? 1 : 0,
},
};
Loading