react-native-screen-transitions 1.0.3 → 1.1.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 +223 -177
- package/dist/index.d.mts +867 -3
- package/dist/index.d.ts +867 -3
- package/dist/index.js +702 -426
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +758 -479
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -6
package/README.md
CHANGED
|
@@ -37,126 +37,106 @@ bun add react-native-screen-transitions
|
|
|
37
37
|
npm install react-native-reanimated react-native-gesture-handler
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
##
|
|
40
|
+
## Your First Screen Transition
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
44
45
|
|
|
45
46
|
```tsx
|
|
46
|
-
// app/_layout.tsx
|
|
47
|
-
import { Stack } from "expo-router";
|
|
47
|
+
// app/_layout.tsx (expo-router) or App.tsx (react-navigation)
|
|
48
48
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
49
49
|
import Transition from "react-native-screen-transitions";
|
|
50
50
|
|
|
51
|
+
export default function RootLayout() {
|
|
52
|
+
return (
|
|
53
|
+
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
54
|
+
{/* Your navigation setup */}
|
|
55
|
+
</GestureHandlerRootView>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Add transition configuration to your screens
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { Stack } from "expo-router";
|
|
64
|
+
import Transition from "react-native-screen-transitions";
|
|
65
|
+
|
|
51
66
|
export default function RootLayout() {
|
|
52
67
|
return (
|
|
53
68
|
<GestureHandlerRootView>
|
|
54
69
|
<Stack>
|
|
55
70
|
<Stack.Screen
|
|
56
|
-
name="
|
|
57
|
-
|
|
71
|
+
name="a"
|
|
72
|
+
options={{ headerShown: false }}
|
|
73
|
+
{...Transition.createScreenConfig()}
|
|
58
74
|
/>
|
|
59
75
|
<Stack.Screen
|
|
60
|
-
name="
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
...l,
|
|
65
|
-
...Transition.presets.SlideFromTop(),
|
|
66
|
-
})
|
|
67
|
-
}
|
|
76
|
+
name="b"
|
|
77
|
+
{...Transition.createScreenConfig({
|
|
78
|
+
...Transition.presets.SlideFromTop(),
|
|
79
|
+
})}
|
|
68
80
|
/>
|
|
69
|
-
{/* Add more screens with presets */}
|
|
70
81
|
</Stack>
|
|
71
82
|
</GestureHandlerRootView>
|
|
72
83
|
);
|
|
73
84
|
}
|
|
74
85
|
```
|
|
75
86
|
|
|
76
|
-
**
|
|
87
|
+
> ⚠️ **Important**: The first screen (like "a" in the example) must include `{...Transition.createScreenConfig()}` for it to be properly controlled by incoming screens.
|
|
77
88
|
|
|
78
|
-
|
|
89
|
+
### 3. Use transition-aware components in your screens
|
|
79
90
|
|
|
80
91
|
```tsx
|
|
81
|
-
//
|
|
82
|
-
import { Stack } from "expo-router";
|
|
92
|
+
// a.tsx
|
|
83
93
|
import Transition from "react-native-screen-transitions";
|
|
84
94
|
|
|
85
|
-
export default function
|
|
95
|
+
export default function A() {
|
|
86
96
|
return (
|
|
87
|
-
<Transition.View>
|
|
88
|
-
|
|
89
|
-
<Stack.Screen name="a" />
|
|
90
|
-
{/* Nested screens */}
|
|
91
|
-
</Stack>
|
|
97
|
+
<Transition.View> {/* By default has flex: 1 */}
|
|
98
|
+
{/* Your content */}
|
|
92
99
|
</Transition.View>
|
|
93
100
|
);
|
|
94
101
|
}
|
|
95
|
-
```
|
|
96
102
|
|
|
97
|
-
|
|
98
|
-
For react-navigation, use `createNativeStackNavigator` and apply transitions via `listeners` and `options`.
|
|
99
|
-
|
|
100
|
-
```tsx
|
|
101
|
-
// App.tsx
|
|
102
|
-
import { createStaticNavigation } from "@react-navigation/native";
|
|
103
|
-
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
|
104
|
-
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
103
|
+
// b.tsx
|
|
105
104
|
import Transition from "react-native-screen-transitions";
|
|
106
|
-
import { Home } from "./screens/Home"; // Your screens
|
|
107
|
-
import { ScreenA } from "./screens/ScreenA";
|
|
108
|
-
|
|
109
|
-
const RootStack = createNativeStackNavigator({
|
|
110
|
-
screens: {
|
|
111
|
-
Home: {
|
|
112
|
-
screen: Home,
|
|
113
|
-
listeners: Transition.createConfig,
|
|
114
|
-
},
|
|
115
|
-
ScreenA: {
|
|
116
|
-
screen: ScreenA,
|
|
117
|
-
options: Transition.defaultScreenOptions(),
|
|
118
|
-
listeners: (l) =>
|
|
119
|
-
Transition.createConfig({
|
|
120
|
-
...l,
|
|
121
|
-
...Transition.presets.SlideFromTop(),
|
|
122
|
-
}),
|
|
123
|
-
},
|
|
124
|
-
// Add more screens
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
105
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
export default function App() {
|
|
106
|
+
export default function B() {
|
|
131
107
|
return (
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
</
|
|
108
|
+
<Transition.View>
|
|
109
|
+
{/* Your content */}
|
|
110
|
+
</Transition.View>
|
|
135
111
|
);
|
|
136
112
|
}
|
|
137
113
|
```
|
|
138
114
|
|
|
139
|
-
For
|
|
140
|
-
|
|
141
|
-
### Predefined Presets
|
|
142
|
-
Use these out-of-the-box animations via `Transition.presets`:
|
|
115
|
+
### For Nested Navigators
|
|
143
116
|
|
|
144
|
-
|
|
145
|
-
- `ZoomIn()`: Screen zooms in from the center.
|
|
146
|
-
- `SlideFromBottom()`: Screen slides in from the bottom.
|
|
147
|
-
- `DraggableCard()`: Interactive card-like drag gesture.
|
|
148
|
-
- `ElasticCard()`: Elastic bounce effect on drag.
|
|
117
|
+
When using nested navigators, wrap the nested Stack in a transition-aware component:
|
|
149
118
|
|
|
150
|
-
Example:
|
|
151
119
|
```tsx
|
|
152
|
-
|
|
120
|
+
// app/nested/_layout.tsx
|
|
121
|
+
import Transition from "react-native-screen-transitions";
|
|
122
|
+
|
|
123
|
+
export default function TabLayout() {
|
|
124
|
+
return (
|
|
125
|
+
<Transition.View>
|
|
126
|
+
<Stack>
|
|
127
|
+
<Stack.Screen name="nested-a" {...Transition.createScreenConfig()} />
|
|
128
|
+
<Stack.Screen name="nested-b" {...Transition.createScreenConfig()} />
|
|
129
|
+
</Stack>
|
|
130
|
+
</Transition.View>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
153
133
|
```
|
|
154
134
|
|
|
155
|
-
|
|
156
|
-
There are two ways to define custom animations: at the navigator level using `screenStyleInterpolator` (recommended for animating both screens simultaneously), or at the screen level using `useScreenAnimation` hook.
|
|
135
|
+
## Advanced Usage
|
|
157
136
|
|
|
158
|
-
|
|
159
|
-
|
|
137
|
+
### Method 1: Navigator-Level Interpolator (Recommended)
|
|
138
|
+
|
|
139
|
+
Define a `screenStyleInterpolator` at the navigator level to animate both entering and exiting screens simultaneously. This approach is much cleaner.
|
|
160
140
|
|
|
161
141
|
```tsx
|
|
162
142
|
// app/_layout.tsx
|
|
@@ -171,53 +151,47 @@ export default function RootLayout() {
|
|
|
171
151
|
<Stack>
|
|
172
152
|
<Stack.Screen
|
|
173
153
|
name="a"
|
|
174
|
-
|
|
154
|
+
{...Transition.createScreenConfig()} // Initialize
|
|
175
155
|
/>
|
|
176
156
|
<Stack.Screen
|
|
177
157
|
name="b"
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
return {
|
|
203
|
-
contentStyle: {
|
|
204
|
-
transform: [{ translateX }],
|
|
205
|
-
},
|
|
206
|
-
};
|
|
207
|
-
},
|
|
208
|
-
transitionSpec: {
|
|
209
|
-
open: {
|
|
210
|
-
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
|
211
|
-
duration: 1000,
|
|
212
|
-
},
|
|
213
|
-
close: {
|
|
214
|
-
damping: 10,
|
|
215
|
-
mass: 0.5,
|
|
216
|
-
stiffness: 100,
|
|
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 }],
|
|
217
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,
|
|
218
192
|
},
|
|
219
|
-
}
|
|
220
|
-
}
|
|
193
|
+
},
|
|
194
|
+
})}
|
|
221
195
|
/>
|
|
222
196
|
</Stack>
|
|
223
197
|
</GestureHandlerRootView>
|
|
@@ -225,60 +199,33 @@ export default function RootLayout() {
|
|
|
225
199
|
}
|
|
226
200
|
```
|
|
227
201
|
|
|
228
|
-
When using `screenStyleInterpolator`, both screens must wrap their content in
|
|
229
|
-
|
|
230
|
-
```tsx
|
|
231
|
-
// a.tsx
|
|
232
|
-
import Transition from 'react-native-screen-transitions';
|
|
233
|
-
|
|
234
|
-
export default function A() {
|
|
235
|
-
return (
|
|
236
|
-
<Transition.View>
|
|
237
|
-
{/* Your content */}
|
|
238
|
-
</Transition.View>
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// b.tsx
|
|
243
|
-
import Transition from 'react-native-screen-transitions';
|
|
202
|
+
When using `screenStyleInterpolator`, both screens must wrap their content in a transition-aware component.
|
|
244
203
|
|
|
245
|
-
|
|
246
|
-
return (
|
|
247
|
-
<Transition.View>
|
|
248
|
-
{/* Your content */}
|
|
249
|
-
</Transition.View>
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
```
|
|
204
|
+
### Method 2: Screen-Level Animations
|
|
253
205
|
|
|
254
|
-
|
|
255
|
-
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.
|
|
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.
|
|
256
207
|
|
|
257
208
|
```tsx
|
|
258
209
|
// app/_layout.tsx
|
|
259
210
|
<Stack.Screen
|
|
260
211
|
name="a"
|
|
261
|
-
|
|
212
|
+
{...Transition.createScreenConfig()}
|
|
262
213
|
/>
|
|
263
214
|
<Stack.Screen
|
|
264
215
|
name="b"
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
open: {
|
|
271
|
-
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
|
272
|
-
duration: 1000,
|
|
273
|
-
},
|
|
274
|
-
close: {
|
|
275
|
-
damping: 10,
|
|
276
|
-
mass: 0.5,
|
|
277
|
-
stiffness: 100,
|
|
278
|
-
},
|
|
216
|
+
{...Transition.createScreenConfig({
|
|
217
|
+
transitionSpec: {
|
|
218
|
+
open: {
|
|
219
|
+
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
|
220
|
+
duration: 1000,
|
|
279
221
|
},
|
|
280
|
-
|
|
281
|
-
|
|
222
|
+
close: {
|
|
223
|
+
damping: 10,
|
|
224
|
+
mass: 0.5,
|
|
225
|
+
stiffness: 100,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
})}
|
|
282
229
|
/>
|
|
283
230
|
```
|
|
284
231
|
|
|
@@ -299,7 +246,7 @@ export default function A() {
|
|
|
299
246
|
});
|
|
300
247
|
|
|
301
248
|
return (
|
|
302
|
-
<Animated.View style={animatedStyle}>
|
|
249
|
+
<Animated.View style={[{ flex: 1 }, animatedStyle]}>
|
|
303
250
|
{/* Your content */}
|
|
304
251
|
</Animated.View>
|
|
305
252
|
);
|
|
@@ -326,30 +273,129 @@ export default function B() {
|
|
|
326
273
|
</Animated.View>
|
|
327
274
|
);
|
|
328
275
|
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
|
|
329
279
|
|
|
280
|
+
## Dismissible Screens with Scrollables
|
|
330
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
|
|
287
|
+
|
|
288
|
+
```tsx
|
|
289
|
+
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
|
+
|
|
294
|
+
// Built-in transition-aware scrollables
|
|
295
|
+
const MyScrollView = Transition.ScrollView;
|
|
296
|
+
const MyFlatList = Transition.FlatList;
|
|
297
|
+
|
|
298
|
+
// Create custom transition-aware scrollables
|
|
299
|
+
const TransitionFlashList = Transition.createTransitionAwareScrollable(FlashList);
|
|
300
|
+
const TransitionLegendList = Transition.createTransitionAwareScrollable(LegendList);
|
|
331
301
|
```
|
|
332
302
|
|
|
303
|
+
These components now integrate seamlessly with your transition system and provide smart gesture handling.
|
|
304
|
+
|
|
305
|
+
### Configure Gesture Directions
|
|
306
|
+
|
|
307
|
+
```tsx
|
|
308
|
+
<Stack.Screen
|
|
309
|
+
name="scrollable-screen"
|
|
310
|
+
{...Transition.createScreenConfig({
|
|
311
|
+
gestureDirection: ["vertical", "vertical-inverted"],
|
|
312
|
+
gestureEnabled: true,
|
|
313
|
+
// ... other config
|
|
314
|
+
})}
|
|
315
|
+
/>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Use in Your Screen
|
|
319
|
+
|
|
320
|
+
```tsx
|
|
321
|
+
// scrollable-screen.tsx
|
|
322
|
+
export default function ScrollableScreen() {
|
|
323
|
+
return (
|
|
324
|
+
<Transition.ScrollView>
|
|
325
|
+
{/* Your scrollable content */}
|
|
326
|
+
</Transition.ScrollView>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Smart Gesture Handling**: The screen will only start the dismissal process when:
|
|
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.
|
|
338
|
+
|
|
339
|
+
## Performance Considerations
|
|
340
|
+
|
|
341
|
+
This package is designed for optimal performance, but since underlying screens remain active during transitions, following these guidelines will help maintain smooth 60fps animations:
|
|
342
|
+
|
|
343
|
+
### Screen Optimization
|
|
344
|
+
- **Keep screens lightweight**: Minimize heavy computations and complex layouts in screens that will be animated
|
|
345
|
+
|
|
346
|
+
### Animation Properties
|
|
347
|
+
Prioritize transform and opacity properties over layout-affecting properties for the smoothest animations:
|
|
348
|
+
|
|
349
|
+
**✅ Performant properties:**
|
|
350
|
+
- `transform` (translateX, translateY, scale, rotate)
|
|
351
|
+
- `opacity`
|
|
352
|
+
- `backgroundColor` (with caution)
|
|
353
|
+
|
|
354
|
+
**❌ Avoid when possible:**
|
|
355
|
+
- Layout properties (`width`, `height`, `padding`, `margin`)
|
|
356
|
+
- `borderRadius` on large elements
|
|
357
|
+
- Complex `shadowOffset` or `elevation` changes
|
|
358
|
+
- Frequent `zIndex` modifications
|
|
359
|
+
|
|
360
|
+
### Easing and Timing Configuration
|
|
361
|
+
|
|
362
|
+
Choose balanced easing curves to avoid perceived delays:
|
|
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.
|
|
387
|
+
|
|
333
388
|
|
|
334
|
-
|
|
335
|
-
This is a WIP package, so expect improvements. Current known issues:
|
|
336
|
-
- **Gestures with ScrollViews**: Gestures can be wonky when combined with scrollable content. For example, if a screen defines vertical dismissal gestures and contains a vertical `ScrollView`, the gesture may not trigger reliably (conflicts with scroll handling).
|
|
337
|
-
- **Gestures Dismisall with Nested Navigators**: When using nested navigators with gesture dismissal enabled, dismissing a nested screen via gesture may cause the transparent modal to appear dismissed while remaining open. This affects the visual state but not the actual navigation state.
|
|
338
|
-
- **Web Support**: Not intended or tested for web—focus is on mobile (iOS/Android). Web may have issues with gestures and animations.
|
|
389
|
+
## Support and Development
|
|
339
390
|
|
|
391
|
+
This package is provided as-is and is developed in my free time. While I strive to maintain and improve it, please understand that:
|
|
340
392
|
|
|
341
|
-
|
|
393
|
+
- **Updates and bug fixes** may take time to implement
|
|
394
|
+
- **Feature requests** will be considered but may not be prioritized immediately
|
|
342
395
|
|
|
343
|
-
|
|
396
|
+
I apologize for any inconvenience this may cause. If you encounter issues or have suggestions, please feel free to open an issue on the repository.
|
|
344
397
|
|
|
345
|
-
Roadmap:
|
|
346
|
-
- Fix gesture conflicts with ScrollViews (e.g., better gesture priority handling).
|
|
347
|
-
- Add more presets and customization options.
|
|
348
|
-
- Improve documentation and examples.
|
|
349
|
-
- Potential web support if demand arises.
|
|
350
|
-
- Testing for more edge cases (e.g., modals, tabs).
|
|
351
398
|
|
|
352
|
-
See the examples in `/examples/expo-router-example` and `/examples/react-nav-example` for full demos.
|
|
353
399
|
|
|
354
400
|
## License
|
|
355
401
|
MIT
|