react-native-screen-transitions 1.0.1
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 +355 -0
- package/dist/index.d.mts +609 -0
- package/dist/index.d.ts +609 -0
- package/dist/index.js +904 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +891 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# react-native-screen-transitions
|
|
2
|
+
|
|
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
|
+
|
|
8
|
+
|
|
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
|
+
|
|
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
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
- Predefined animation presets (e.g., SlideFromTop, ZoomIn, DraggableCard).
|
|
18
|
+
- Gesture support for interactive transitions (e.g., drag-to-dismiss).
|
|
19
|
+
- Animations using Reanimated.
|
|
20
|
+
- Easy integration with expo-router and react-navigation.
|
|
21
|
+
|
|
22
|
+
## Compatibility
|
|
23
|
+
- **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.
|
|
24
|
+
- **Dependencies**: Requires React Native, Reanimated, Gesture Handler, and either expo-router or react-navigation.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
```bash
|
|
28
|
+
npm install react-native-screen-transitions
|
|
29
|
+
# or
|
|
30
|
+
yarn add react-native-screen-transitions
|
|
31
|
+
# or
|
|
32
|
+
bun add react-native-screen-transitions
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Peer Dependencies
|
|
36
|
+
```bash
|
|
37
|
+
npm install react-native-reanimated react-native-gesture-handler
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### Integration with expo-router
|
|
43
|
+
In expo-router, you can define transitions in your root layout (`app/_layout.tsx`) using the `listeners` prop on `Stack.Screen`. Wrap your app in `GestureHandlerRootView` for gesture support.
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// app/_layout.tsx
|
|
47
|
+
import { Stack } from "expo-router";
|
|
48
|
+
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
49
|
+
import Transition from "react-native-screen-transitions";
|
|
50
|
+
|
|
51
|
+
export default function RootLayout() {
|
|
52
|
+
return (
|
|
53
|
+
<GestureHandlerRootView>
|
|
54
|
+
<Stack>
|
|
55
|
+
<Stack.Screen
|
|
56
|
+
name="index"
|
|
57
|
+
listeners={Transition.createConfig}
|
|
58
|
+
/>
|
|
59
|
+
<Stack.Screen
|
|
60
|
+
name="a"
|
|
61
|
+
options={Transition.defaultScreenOptions()}
|
|
62
|
+
listeners={(l) =>
|
|
63
|
+
Transition.createConfig({
|
|
64
|
+
...l,
|
|
65
|
+
...Transition.presets.SlideFromTop(),
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
/>
|
|
69
|
+
{/* Add more screens with presets */}
|
|
70
|
+
</Stack>
|
|
71
|
+
</GestureHandlerRootView>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Note**: `Transition.defaultScreenOptions()` returns the required screen options for animations to work properly. It sets `presentation: "containedTransparentModal"`, `headerShown: false`, and `animation: "none"` to ensure the library can control the transition animations.
|
|
77
|
+
|
|
78
|
+
For grouped routes with layouts (e.g., `app/group-a/_layout.tsx`), wrap the nested `Stack` in `Transition.View` to enable transitions:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
// app/group-a/_layout.tsx
|
|
82
|
+
import { Stack } from "expo-router";
|
|
83
|
+
import Transition from "react-native-screen-transitions";
|
|
84
|
+
|
|
85
|
+
export default function GroupLayout() {
|
|
86
|
+
return (
|
|
87
|
+
<Transition.View>
|
|
88
|
+
<Stack>
|
|
89
|
+
<Stack.Screen name="a" />
|
|
90
|
+
{/* Nested screens */}
|
|
91
|
+
</Stack>
|
|
92
|
+
</Transition.View>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Integration with react-navigation
|
|
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";
|
|
105
|
+
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
|
+
|
|
128
|
+
const Navigation = createStaticNavigation(RootStack);
|
|
129
|
+
|
|
130
|
+
export default function App() {
|
|
131
|
+
return (
|
|
132
|
+
<GestureHandlerRootView>
|
|
133
|
+
<Navigation />
|
|
134
|
+
</GestureHandlerRootView>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
For nested navigators, wrap them in `Transition.View` similar to expo-router.
|
|
140
|
+
|
|
141
|
+
### Predefined Presets
|
|
142
|
+
Use these out-of-the-box animations via `Transition.presets`:
|
|
143
|
+
|
|
144
|
+
- `SlideFromTop()`: Screen slides in from the top.
|
|
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.
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
```tsx
|
|
152
|
+
listeners={(l) => Transition.createConfig({ ...l, ...Transition.presets.DraggableCard() })}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Defining your own screen animations
|
|
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.
|
|
157
|
+
|
|
158
|
+
#### Method 1: Navigator-Level Interpolator (Recommended)
|
|
159
|
+
Define a `screenStyleInterpolator` at the navigator level to animate both the entering and exiting screens simultaneously. This approach provides the most control.
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
// app/_layout.tsx
|
|
163
|
+
import { Stack } from "expo-router";
|
|
164
|
+
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
165
|
+
import Transition from "react-native-screen-transitions";
|
|
166
|
+
import { interpolate, Easing } from "react-native-reanimated";
|
|
167
|
+
|
|
168
|
+
export default function RootLayout() {
|
|
169
|
+
return (
|
|
170
|
+
<GestureHandlerRootView>
|
|
171
|
+
<Stack>
|
|
172
|
+
<Stack.Screen
|
|
173
|
+
name="a"
|
|
174
|
+
listeners={Transition.createConfig} // Add blank config so system knows what to animate
|
|
175
|
+
/>
|
|
176
|
+
<Stack.Screen
|
|
177
|
+
name="b"
|
|
178
|
+
options={Transition.defaultScreenOptions()}
|
|
179
|
+
listeners={(l) =>
|
|
180
|
+
Transition.createConfig({
|
|
181
|
+
...l,
|
|
182
|
+
gestureDirection: "horizontal",
|
|
183
|
+
gestureEnabled: true,
|
|
184
|
+
gestureResponseDistance: 50,
|
|
185
|
+
gestureVelocityImpact: 0.3,
|
|
186
|
+
screenStyleInterpolator: ({
|
|
187
|
+
current,
|
|
188
|
+
next,
|
|
189
|
+
layouts: { screen: { width } },
|
|
190
|
+
}) => {
|
|
191
|
+
"worklet";
|
|
192
|
+
|
|
193
|
+
const progress = current.progress.value + (next?.progress.value || 0);
|
|
194
|
+
|
|
195
|
+
const translateX = interpolate(
|
|
196
|
+
progress,
|
|
197
|
+
[0, 1, 2],
|
|
198
|
+
[width, 0, width * -0.3],
|
|
199
|
+
"clamp"
|
|
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,
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
/>
|
|
222
|
+
</Stack>
|
|
223
|
+
</GestureHandlerRootView>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
When using `screenStyleInterpolator`, both screens must wrap their content in `Transition.View`:
|
|
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';
|
|
244
|
+
|
|
245
|
+
export default function B() {
|
|
246
|
+
return (
|
|
247
|
+
<Transition.View>
|
|
248
|
+
{/* Your content */}
|
|
249
|
+
</Transition.View>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### Method 2: Screen-Level Animations
|
|
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.
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
// app/_layout.tsx
|
|
259
|
+
<Stack.Screen
|
|
260
|
+
name="a"
|
|
261
|
+
listeners={Transition.createConfig}
|
|
262
|
+
/>
|
|
263
|
+
<Stack.Screen
|
|
264
|
+
name="b"
|
|
265
|
+
options={Transition.defaultScreenOptions()}
|
|
266
|
+
listeners={(l) =>
|
|
267
|
+
Transition.createConfig({
|
|
268
|
+
...l,
|
|
269
|
+
transitionSpec: {
|
|
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
|
+
},
|
|
279
|
+
},
|
|
280
|
+
})
|
|
281
|
+
}
|
|
282
|
+
/>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
```tsx
|
|
286
|
+
// a.tsx (previous screen)
|
|
287
|
+
import { useScreenAnimation } from 'react-native-screen-transitions';
|
|
288
|
+
import Animated, { useAnimatedStyle, interpolate } from 'react-native-reanimated';
|
|
289
|
+
|
|
290
|
+
export default function A() {
|
|
291
|
+
const { next, layouts: { screen: { width } } } = useScreenAnimation();
|
|
292
|
+
|
|
293
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
294
|
+
// Unfocusing animation - screen slides left when next screen enters
|
|
295
|
+
const translateX = interpolate(next?.progress.value || 0, [0, 1], [0, width * -0.3]);
|
|
296
|
+
return {
|
|
297
|
+
transform: [{ translateX }],
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<Animated.View style={animatedStyle}>
|
|
303
|
+
{/* Your content */}
|
|
304
|
+
</Animated.View>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// b.tsx (entering screen)
|
|
309
|
+
import { useScreenAnimation } from 'react-native-screen-transitions';
|
|
310
|
+
import Animated, { useAnimatedStyle, interpolate } from 'react-native-reanimated';
|
|
311
|
+
|
|
312
|
+
export default function B() {
|
|
313
|
+
const { current, layouts: { screen: { width } } } = useScreenAnimation();
|
|
314
|
+
|
|
315
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
316
|
+
// Focusing animation - screen slides in from right
|
|
317
|
+
const translateX = interpolate(current.progress.value, [0, 1], [width, 0]);
|
|
318
|
+
return {
|
|
319
|
+
transform: [{ translateX }],
|
|
320
|
+
};
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
return (
|
|
324
|
+
<Animated.View style={[{ flex: 1 }, animatedStyle]}>
|
|
325
|
+
{/* Your content */}
|
|
326
|
+
</Animated.View>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
### Known Issues and Roadmap
|
|
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
|
+
- **Web Support**: Not intended or tested for web—focus is on mobile (iOS/Android). Web may have issues with gestures and animations.
|
|
338
|
+
- **Other**: Limited testing in complex nested navigators; potential edge cases with rapid transitions.
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
### Note:
|
|
342
|
+
|
|
343
|
+
This package is something I'll work on in my freetime. However, you can see the roadmap I plan on following in the coming future.
|
|
344
|
+
|
|
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
|
+
|
|
352
|
+
See the examples in `/examples/expo-router-example` and `/examples/react-nav-example` for full demos.
|
|
353
|
+
|
|
354
|
+
## License
|
|
355
|
+
MIT
|