react-native-screen-transitions 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +199 -250
- package/dist/index.d.mts +69 -29
- package/dist/index.d.ts +69 -29
- package/dist/index.js +119 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +125 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +54 -50
package/README.md
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
# react-native-screen-transitions
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
| iOS | Android |
|
|
5
|
-
|---------|---------|
|
|
6
|
-
|<video src="https://github.com/user-attachments/assets/8a7b8006-f165-4a78-b0f9-c94cddd948b9" width="300" controls></video>|<video src="https://github.com/user-attachments/assets/ddebdaa8-a929-43ab-b857-08a00e142343" width="300" controls></video>|
|
|
7
4
|
|
|
8
5
|
|
|
9
|
-
**WIP**: This package is a work-in-progress. It provides customizable screen transition animations for React Native apps, primarily designed for use with `expo-router` and `react-navigation`. It supports gestures, predefined presets, and custom animations, making it easy to add polished transitions to your navigation flows.
|
|
10
6
|
|
|
11
|
-
This library is inspired by the transition system in `@react-navigation/stack` (not the native stack). If you're familiar with how transitions work there (e.g., using interpolators), you'll find this similar.
|
|
12
7
|
|
|
13
8
|
|
|
14
9
|
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
| iOS | Android |
|
|
21
|
+
|---|---|
|
|
22
|
+
| <video src="https://github.com/user-attachments/assets/81f39391-80c0-4ce4-b6ff-76de85d2cf03" width="300" height="600" controls></video> | <video src="https://github.com/user-attachments/assets/c2b4c6ca-2b0c-4cf4-a164-f7e68cee0c32" width="300" controls></video> |
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
**WIP**: This package is a work-in-progress. It provides customizable screen transition animations for React Native apps, primarily designed for use with `expo-router` and `react-navigation`. It supports gestures, predefined presets, and custom animations, making it easy to add polished transitions to your navigation flows.
|
|
21
26
|
|
|
22
27
|
## Compatibility
|
|
23
28
|
- **Platforms**: Currently tested on iOS and Android. Not tested or intended for web—web support is not a priority and may not work due to gesture and animation differences.
|
|
@@ -34,214 +39,197 @@ bun add react-native-screen-transitions
|
|
|
34
39
|
|
|
35
40
|
## Peer Dependencies
|
|
36
41
|
```bash
|
|
37
|
-
npm install react-native-reanimated react-native-gesture-handler
|
|
42
|
+
npm install react-native-reanimated react-native-gesture-handler @react-navigation/native-stack
|
|
38
43
|
```
|
|
39
44
|
|
|
40
|
-
##
|
|
41
|
-
|
|
42
|
-
Getting started with screen transitions is simple. Here's how to add your first animated transition:
|
|
43
|
-
|
|
44
|
-
### 1. Wrap your app with GestureHandlerRootView
|
|
45
|
+
## Setup
|
|
45
46
|
|
|
47
|
+
### 1. React Navigation
|
|
46
48
|
```tsx
|
|
47
|
-
|
|
48
|
-
import { GestureHandlerRootView } from
|
|
49
|
-
import Transition from
|
|
49
|
+
import { NavigationContainer } from '@react-navigation/native';
|
|
50
|
+
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
51
|
+
import Transition from 'react-native-screen-transitions';
|
|
52
|
+
|
|
53
|
+
const Stack = Transition.createTransitionableStackNavigator();
|
|
50
54
|
|
|
51
|
-
export default function
|
|
55
|
+
export default function App() {
|
|
52
56
|
return (
|
|
53
|
-
<GestureHandlerRootView
|
|
54
|
-
|
|
57
|
+
<GestureHandlerRootView>
|
|
58
|
+
<NavigationContainer>
|
|
59
|
+
<Stack.Navigator>
|
|
60
|
+
<Stack.Screen
|
|
61
|
+
name="Home"
|
|
62
|
+
component={Home}
|
|
63
|
+
options={{
|
|
64
|
+
skipDefaultScreenOptions: true, // prevents transparent-modal default
|
|
65
|
+
}}
|
|
66
|
+
/>
|
|
67
|
+
<Stack.Screen
|
|
68
|
+
name="A"
|
|
69
|
+
component={ScreenA}
|
|
70
|
+
options={{
|
|
71
|
+
...Transition.presets.SlideFromTop(),
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
</Stack.Navigator>
|
|
75
|
+
</NavigationContainer>
|
|
55
76
|
</GestureHandlerRootView>
|
|
56
77
|
);
|
|
57
78
|
}
|
|
58
79
|
```
|
|
59
80
|
|
|
60
|
-
### 2.
|
|
81
|
+
### 2. Expo Router
|
|
82
|
+
Use the withLayoutContext to convert it into an expo router compatible navigator.
|
|
61
83
|
|
|
62
84
|
```tsx
|
|
63
|
-
import {
|
|
64
|
-
import Transition
|
|
85
|
+
import { withLayoutContext } from 'expo-router';
|
|
86
|
+
import Transition, {
|
|
87
|
+
type TransitionStackNavigatorTypeBag,
|
|
88
|
+
} from 'react-native-screen-transitions';
|
|
89
|
+
|
|
90
|
+
const TransitionableNativeStack =
|
|
91
|
+
Transition.createTransitionableStackNavigator();
|
|
92
|
+
|
|
93
|
+
export const Stack = withLayoutContext<
|
|
94
|
+
TransitionStackNavigatorTypeBag['ScreenOptions'],
|
|
95
|
+
typeof TransitionableNativeStack.Navigator,
|
|
96
|
+
TransitionStackNavigatorTypeBag['State'],
|
|
97
|
+
TransitionStackNavigatorTypeBag['EventMap']
|
|
98
|
+
>(TransitionableNativeStack.Navigator);
|
|
99
|
+
```
|
|
65
100
|
|
|
66
|
-
|
|
67
|
-
|
|
101
|
+
Use it exactly like any other Expo Router layout:
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
import { Stack } from './layouts/stack.tsx'
|
|
105
|
+
|
|
106
|
+
export default function RootLayout(){
|
|
107
|
+
return(
|
|
68
108
|
<GestureHandlerRootView>
|
|
69
109
|
<Stack>
|
|
70
110
|
<Stack.Screen
|
|
71
111
|
name="a"
|
|
72
|
-
options={{
|
|
73
|
-
|
|
112
|
+
options={{
|
|
113
|
+
// You usually don't want your first screen to be a transparent modal.
|
|
114
|
+
skipDefaultScreenOptions: true,
|
|
115
|
+
}}
|
|
74
116
|
/>
|
|
75
117
|
<Stack.Screen
|
|
76
118
|
name="b"
|
|
77
|
-
{
|
|
119
|
+
options={{
|
|
78
120
|
...Transition.presets.SlideFromTop(),
|
|
79
|
-
}
|
|
121
|
+
}}
|
|
80
122
|
/>
|
|
81
123
|
</Stack>
|
|
82
124
|
</GestureHandlerRootView>
|
|
83
|
-
)
|
|
125
|
+
)
|
|
84
126
|
}
|
|
85
127
|
```
|
|
86
128
|
|
|
87
|
-
>
|
|
129
|
+
> **Note**: `Transition.createTransitionableStackNavigator()` returns a Native Stack Navigator that's been injected with the necessary functionality for screen transitions to work.
|
|
88
130
|
|
|
89
|
-
### 3. Use transition-aware components in your screens
|
|
90
131
|
|
|
91
|
-
|
|
92
|
-
// a.tsx
|
|
93
|
-
import Transition from "react-native-screen-transitions";
|
|
132
|
+
## Creating your screen animations
|
|
94
133
|
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<Transition.View> {/* By default has flex: 1 */}
|
|
98
|
-
{/* Your content */}
|
|
99
|
-
</Transition.View>
|
|
100
|
-
);
|
|
101
|
-
}
|
|
134
|
+
### Using presets
|
|
102
135
|
|
|
103
|
-
|
|
104
|
-
|
|
136
|
+
Pick a built-in preset and spread it into the screen’s options.
|
|
137
|
+
The incoming screen automatically controls the previous screen.
|
|
105
138
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
139
|
+
```tsx
|
|
140
|
+
<Stack>
|
|
141
|
+
<Stack.Screen
|
|
142
|
+
name="a"
|
|
143
|
+
options={{
|
|
144
|
+
// avoids transparent-modal default for first screen
|
|
145
|
+
skipDefaultScreenOptions: true,
|
|
146
|
+
}}
|
|
147
|
+
/>
|
|
148
|
+
<Stack.Screen
|
|
149
|
+
name="b"
|
|
150
|
+
options={{
|
|
151
|
+
...Transition.presets.SlideFromTop(),
|
|
152
|
+
}}
|
|
153
|
+
/>
|
|
154
|
+
<Stack.Screen
|
|
155
|
+
name="c"
|
|
156
|
+
options={{
|
|
157
|
+
...Transition.presets.SlideFromBottom(),
|
|
158
|
+
}}
|
|
159
|
+
/>
|
|
160
|
+
</Stack>
|
|
113
161
|
```
|
|
114
162
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
163
|
+
> ⚠️ **Important**
|
|
164
|
+
> Any screen that **must** participate in a transition (i.e., be animated) **must** be wrapped in a transition-aware component.
|
|
165
|
+
> For example, if both `a` and `b` are meant to animate, wrap each screen’s root like this:
|
|
118
166
|
|
|
119
167
|
```tsx
|
|
120
|
-
//
|
|
121
|
-
|
|
168
|
+
// a.tsx
|
|
169
|
+
<Transition.View>
|
|
170
|
+
...
|
|
171
|
+
</Transition.View>
|
|
122
172
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
<Stack.Screen name="nested-a" {...Transition.createScreenConfig()} />
|
|
128
|
-
<Stack.Screen name="nested-b" {...Transition.createScreenConfig()} />
|
|
129
|
-
</Stack>
|
|
130
|
-
</Transition.View>
|
|
131
|
-
);
|
|
132
|
-
}
|
|
173
|
+
// b.tsx
|
|
174
|
+
<Transition.View>
|
|
175
|
+
...
|
|
176
|
+
</Transition.View>
|
|
133
177
|
```
|
|
134
178
|
|
|
135
|
-
|
|
179
|
+
Without this wrapper, the transition system cannot animate the screen and the animation will appear broken or skipped.
|
|
136
180
|
|
|
137
|
-
### Method 1: Navigator-Level Interpolator (Recommended)
|
|
138
181
|
|
|
139
|
-
Define a `screenStyleInterpolator` at the navigator level to animate both entering and exiting screens simultaneously. This approach is much cleaner.
|
|
140
182
|
|
|
141
|
-
|
|
142
|
-
// app/_layout.tsx
|
|
143
|
-
import { Stack } from "expo-router";
|
|
144
|
-
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
145
|
-
import Transition from "react-native-screen-transitions";
|
|
146
|
-
import { interpolate, Easing } from "react-native-reanimated";
|
|
183
|
+
### Navigator-level custom animations
|
|
147
184
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<GestureHandlerRootView>
|
|
151
|
-
<Stack>
|
|
152
|
-
<Stack.Screen
|
|
153
|
-
name="a"
|
|
154
|
-
{...Transition.createScreenConfig()} // Initialize
|
|
155
|
-
/>
|
|
156
|
-
<Stack.Screen
|
|
157
|
-
name="b"
|
|
158
|
-
{...Transition.createScreenConfig({
|
|
159
|
-
gestureDirection: "horizontal",
|
|
160
|
-
gestureEnabled: true,
|
|
161
|
-
gestureResponseDistance: 50,
|
|
162
|
-
gestureVelocityImpact: 0.3,
|
|
163
|
-
screenStyleInterpolator: ({
|
|
164
|
-
current,
|
|
165
|
-
next,
|
|
166
|
-
layouts: { screen: { width } },
|
|
167
|
-
}) => {
|
|
168
|
-
"worklet";
|
|
169
|
-
// Mimics the iOS stack slide animation
|
|
170
|
-
const progress = current.progress.value + (next?.progress.value || 0);
|
|
171
|
-
const translateX = interpolate(
|
|
172
|
-
progress,
|
|
173
|
-
[0, 1, 2],
|
|
174
|
-
[width, 0, width * -0.3],
|
|
175
|
-
"clamp"
|
|
176
|
-
);
|
|
177
|
-
return {
|
|
178
|
-
contentStyle: {
|
|
179
|
-
transform: [{ translateX }],
|
|
180
|
-
},
|
|
181
|
-
};
|
|
182
|
-
},
|
|
183
|
-
transitionSpec: {
|
|
184
|
-
open: {
|
|
185
|
-
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
|
186
|
-
duration: 1000,
|
|
187
|
-
},
|
|
188
|
-
close: {
|
|
189
|
-
damping: 10,
|
|
190
|
-
mass: 0.5,
|
|
191
|
-
stiffness: 100,
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
})}
|
|
195
|
-
/>
|
|
196
|
-
</Stack>
|
|
197
|
-
</GestureHandlerRootView>
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
When using `screenStyleInterpolator`, both screens must wrap their content in a transition-aware component.
|
|
203
|
-
|
|
204
|
-
### Method 2: Screen-Level Animations
|
|
205
|
-
|
|
206
|
-
Alternatively, define animations at the screen level using the `useScreenAnimation` hook. This is useful for screen-specific effects or when you don't need to animate both screens. You CAN combine this with `screenStyleInterpolator` for more advanced animations, but for this example, we'll leave `screenStyleInterpolator` undefined.
|
|
185
|
+
Instead of presets, you can define a custom transition directly on the screen’s options.
|
|
186
|
+
The `screenStyleInterpolator` receives the current and next screen’s progress and lets you animate both at once.
|
|
207
187
|
|
|
208
188
|
```tsx
|
|
209
|
-
|
|
210
|
-
<Stack.Screen
|
|
211
|
-
name="a"
|
|
212
|
-
{...Transition.createScreenConfig()}
|
|
213
|
-
/>
|
|
189
|
+
import { interpolate } from 'react-native-reanimated'
|
|
214
190
|
<Stack.Screen
|
|
215
191
|
name="b"
|
|
216
|
-
{
|
|
192
|
+
options={{
|
|
193
|
+
screenStyleInterpolator: ({
|
|
194
|
+
current,
|
|
195
|
+
next,
|
|
196
|
+
layouts: { screen: { width } },
|
|
197
|
+
}) => {
|
|
198
|
+
"worklet";
|
|
199
|
+
|
|
200
|
+
const progress = current.progress.value + (next?.progress.value ?? 0);
|
|
201
|
+
|
|
202
|
+
const x = interpolate(progress, [0, 1, 2], [width, 0, -width]);
|
|
203
|
+
return {
|
|
204
|
+
contentStyle: {
|
|
205
|
+
transform: [{ translateX: x }],
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
},
|
|
217
209
|
transitionSpec: {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
duration: 1000,
|
|
221
|
-
},
|
|
222
|
-
close: {
|
|
223
|
-
damping: 10,
|
|
224
|
-
mass: 0.5,
|
|
225
|
-
stiffness: 100,
|
|
226
|
-
},
|
|
210
|
+
close: Transition.specs.DefaultSpec,
|
|
211
|
+
open: Transition.specs.DefaultSpec,
|
|
227
212
|
},
|
|
228
|
-
}
|
|
213
|
+
}}
|
|
229
214
|
/>
|
|
230
215
|
```
|
|
231
216
|
|
|
217
|
+
In this example the incoming screen slides in from the right while the exiting screen slides out to the left.
|
|
218
|
+
|
|
219
|
+
### Screen-level custom animations with `useScreenAnimation`
|
|
220
|
+
|
|
221
|
+
For per-screen control, import the `useScreenAnimation` hook and compose your own animated styles.
|
|
222
|
+
|
|
232
223
|
```tsx
|
|
233
|
-
// a.tsx (previous screen)
|
|
234
224
|
import { useScreenAnimation } from 'react-native-screen-transitions';
|
|
235
225
|
import Animated, { useAnimatedStyle, interpolate } from 'react-native-reanimated';
|
|
236
226
|
|
|
237
|
-
export default function
|
|
238
|
-
const {
|
|
227
|
+
export default function BScreen() {
|
|
228
|
+
const { current } = useScreenAnimation();
|
|
239
229
|
|
|
240
230
|
const animatedStyle = useAnimatedStyle(() => {
|
|
241
|
-
// Unfocusing animation - screen slides left when next screen enters
|
|
242
|
-
const translateX = interpolate(next?.progress.value || 0, [0, 1], [0, width * -0.3]);
|
|
243
231
|
return {
|
|
244
|
-
|
|
232
|
+
opacity: current.progress.value
|
|
245
233
|
};
|
|
246
234
|
});
|
|
247
235
|
|
|
@@ -251,139 +239,99 @@ export default function A() {
|
|
|
251
239
|
</Animated.View>
|
|
252
240
|
);
|
|
253
241
|
}
|
|
242
|
+
```
|
|
254
243
|
|
|
255
|
-
|
|
256
|
-
import { useScreenAnimation } from 'react-native-screen-transitions';
|
|
257
|
-
import Animated, { useAnimatedStyle, interpolate } from 'react-native-reanimated';
|
|
244
|
+
## Apply screen transitions to nested navigators
|
|
258
245
|
|
|
259
|
-
|
|
260
|
-
const { current, layouts: { screen: { width } } } = useScreenAnimation();
|
|
246
|
+
When a screen contains its own stack (e.g., `b` is a nested navigator), wrap the nested `Stack` in a `Transition.View`.
|
|
261
247
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
transform: [{ translateX }],
|
|
267
|
-
};
|
|
268
|
-
});
|
|
248
|
+
```tsx
|
|
249
|
+
// app/b/_layout.tsx (nested stack for screen "b")
|
|
250
|
+
import { Stack } from 'expo-router';
|
|
251
|
+
import Transition from 'react-native-screen-transitions';
|
|
269
252
|
|
|
253
|
+
export default function BLayout() {
|
|
270
254
|
return (
|
|
271
|
-
<
|
|
272
|
-
|
|
273
|
-
|
|
255
|
+
<Transition.View>
|
|
256
|
+
<Stack>
|
|
257
|
+
<Stack.Screen name="index" options={{ headerShown: false }} />
|
|
258
|
+
<Stack.Screen name="details" options={{ headerShown: false }} />
|
|
259
|
+
</Stack>
|
|
260
|
+
</Transition.View>
|
|
274
261
|
);
|
|
275
262
|
}
|
|
276
263
|
```
|
|
277
264
|
|
|
265
|
+
The outer transition now treats the entire nested stack as a single animatable view while each inner screen can still function normally—whether you keep the native stack or swap it for the Transitionable Stack.
|
|
278
266
|
|
|
267
|
+
## Swipe-to-dismiss with scrollables
|
|
279
268
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
Screen transitions can be dismissed based on defined gesture directions. Integration with scrollable components is seamless:
|
|
283
|
-
|
|
284
|
-
### Create Custom Transition-Aware Scrollables
|
|
285
|
-
|
|
286
|
-
You can use built-in scrollables or create your own
|
|
269
|
+
You can drag a screen away even when it contains a scroll view.
|
|
270
|
+
Just swap the regular scrollable for a transition-aware one:
|
|
287
271
|
|
|
288
272
|
```tsx
|
|
273
|
+
import Transition from 'react-native-screen-transitions';
|
|
274
|
+
import { LegendList } from "@legendapp/list"
|
|
289
275
|
import { FlashList } from "@shopify/flash-list";
|
|
290
|
-
import { LegendList } from "@legendapp/list";
|
|
291
|
-
import { FlatList, ScrollView } from "react-native";
|
|
292
|
-
import Transition from "react-native-screen-transitions";
|
|
293
276
|
|
|
294
|
-
//
|
|
295
|
-
const
|
|
296
|
-
const
|
|
277
|
+
// Drop-in replacements
|
|
278
|
+
const ScrollView = Transition.ScrollView;
|
|
279
|
+
const FlatList = Transition.FlatList;
|
|
297
280
|
|
|
298
|
-
//
|
|
299
|
-
const TransitionFlashList =
|
|
300
|
-
|
|
301
|
-
```
|
|
281
|
+
// Or wrap any list you like
|
|
282
|
+
const TransitionFlashList =
|
|
283
|
+
Transition.createTransitionAwareScrollable(FlashList);
|
|
302
284
|
|
|
303
|
-
|
|
285
|
+
const TransitionLegendList =
|
|
286
|
+
Transition.createTransitionAwareScrollable(LegendList);
|
|
287
|
+
```
|
|
304
288
|
|
|
305
|
-
|
|
289
|
+
Enable the gesture on the screen:
|
|
306
290
|
|
|
307
291
|
```tsx
|
|
308
292
|
<Stack.Screen
|
|
309
|
-
name="
|
|
310
|
-
{
|
|
311
|
-
gestureDirection: ["vertical", "vertical-inverted"],
|
|
293
|
+
name="gallery"
|
|
294
|
+
options={{
|
|
312
295
|
gestureEnabled: true,
|
|
313
|
-
//
|
|
314
|
-
}
|
|
296
|
+
gestureDirection: 'vertical', // or 'horizontal', ['vertical', 'horizontal'], etc.
|
|
297
|
+
}}
|
|
315
298
|
/>
|
|
316
299
|
```
|
|
317
300
|
|
|
318
|
-
|
|
301
|
+
Use it in the screen:
|
|
319
302
|
|
|
320
303
|
```tsx
|
|
321
|
-
|
|
322
|
-
export default function ScrollableScreen() {
|
|
304
|
+
export default function B() {
|
|
323
305
|
return (
|
|
324
306
|
<Transition.ScrollView>
|
|
325
|
-
{/*
|
|
307
|
+
{/* content */}
|
|
326
308
|
</Transition.ScrollView>
|
|
327
309
|
);
|
|
328
310
|
}
|
|
329
311
|
```
|
|
330
312
|
|
|
331
|
-
|
|
332
|
-
- `vertical`: ScrollView is at the top (scrollY <= 0)
|
|
333
|
-
- `vertical-inverted`: ScrollView is at the bottom (scrollY >= maxScroll)
|
|
334
|
-
- `horizontal`: ScrollView is at the left edge
|
|
335
|
-
- `horizontal-inverted`: ScrollView is at the right edge
|
|
336
|
-
|
|
337
|
-
This prevents gesture conflicts and provides intuitive user interaction.
|
|
313
|
+
Gesture rules (handled automatically):
|
|
338
314
|
|
|
339
|
-
|
|
315
|
+
- **vertical** – only starts when the list is at the very top
|
|
316
|
+
- **vertical-inverted** – only starts when the list is at the very bottom
|
|
317
|
+
- **horizontal** / **horizontal-inverted** – only starts when the list is at the left or right edge
|
|
340
318
|
|
|
341
|
-
|
|
319
|
+
These rules apply **only when the screen contains a nested scrollable**.
|
|
320
|
+
If no scroll view is present, the gesture can begin from **anywhere on the screen**—not restricted to the edges.
|
|
342
321
|
|
|
343
|
-
|
|
344
|
-
- **Keep screens lightweight**: Minimize heavy computations and complex layouts in screens that will be animated
|
|
322
|
+
## Performance tips
|
|
345
323
|
|
|
346
|
-
|
|
347
|
-
Prioritize transform and opacity properties over layout-affecting properties for the smoothest animations:
|
|
324
|
+
Keep animations smooth and responsive:
|
|
348
325
|
|
|
349
|
-
|
|
350
|
-
- `transform`
|
|
351
|
-
-
|
|
352
|
-
- `backgroundColor` (with caution)
|
|
326
|
+
- **Optimize screen content** – avoid heavy computations, complex layouts, or expensive renders in screens that animate
|
|
327
|
+
- **Use GPU-friendly properties** – stick to `transform` and `opacity` which are hardware-accelerated; avoid animating `width`, `height`, `padding`, large `borderRadius`, and complex shadows
|
|
328
|
+
- **Choose appropriate timing** – use natural easing curves and durations that feel responsive without being jarring
|
|
353
329
|
|
|
354
|
-
|
|
355
|
-
- Layout properties (`width`, `height`, `padding`, `margin`)
|
|
356
|
-
- `borderRadius` on large elements
|
|
357
|
-
- Complex `shadowOffset` or `elevation` changes
|
|
358
|
-
- Frequent `zIndex` modifications
|
|
330
|
+
## Roadmap
|
|
359
331
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
```tsx
|
|
365
|
-
// ✅ Good - Smooth and responsive
|
|
366
|
-
transitionSpec: {
|
|
367
|
-
open: {
|
|
368
|
-
duration: 300,
|
|
369
|
-
easing: Easing.bezier(0.25, 0.1, 0.25, 1), // iOS-like easing
|
|
370
|
-
},
|
|
371
|
-
close: {
|
|
372
|
-
duration: 250,
|
|
373
|
-
easing: Easing.out(Easing.quad),
|
|
374
|
-
},
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// ❌ Avoid - Too snappy, may cause perceived delays
|
|
378
|
-
transitionSpec: {
|
|
379
|
-
close: {
|
|
380
|
-
duration: 400,
|
|
381
|
-
easing: Easing.bezierFn(0, 0.98, 0, 1), // Too abrupt
|
|
382
|
-
},
|
|
383
|
-
}
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
**Why timing matters**: Screen dismissal callbacks execute when animations complete. Overly snappy configurations can create a perceived delay between gesture end and actual screen dismissal. Find the sweet spot that matches your app's personality while feeling responsive.
|
|
332
|
+
- **Shared element transitions** – seamless hand-off of components between screens
|
|
333
|
+
- **Gesture-driven forward navigation** – allow gestures to trigger push navigation events
|
|
334
|
+
- **Performance maximization** – further reduce JS thread work and leverage Reanimated 3’s new APIs for even smoother 60 fps animations
|
|
387
335
|
|
|
388
336
|
|
|
389
337
|
## Support and Development
|
|
@@ -397,5 +345,6 @@ I apologize for any inconvenience this may cause. If you encounter issues or hav
|
|
|
397
345
|
|
|
398
346
|
|
|
399
347
|
|
|
348
|
+
|
|
400
349
|
## License
|
|
401
350
|
MIT
|