react-native-screen-transitions 1.0.3 → 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 +240 -245
- package/dist/index.d.mts +909 -5
- package/dist/index.d.ts +909 -5
- package/dist/index.js +816 -426
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +820 -421
- package/dist/index.mjs.map +1 -1
- package/package.json +54 -54
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,322 +39,312 @@ 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
|
|
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
|
-
}
|
|
42
|
+
npm install react-native-reanimated react-native-gesture-handler @react-navigation/native-stack
|
|
74
43
|
```
|
|
75
44
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
For grouped routes with layouts (e.g., `app/group-a/_layout.tsx`), wrap the nested `Stack` in `Transition.View` to enable transitions:
|
|
45
|
+
## Setup
|
|
79
46
|
|
|
47
|
+
### 1. React Navigation
|
|
80
48
|
```tsx
|
|
81
|
-
|
|
82
|
-
import {
|
|
83
|
-
import Transition from
|
|
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
|
-
});
|
|
49
|
+
import { NavigationContainer } from '@react-navigation/native';
|
|
50
|
+
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
51
|
+
import Transition from 'react-native-screen-transitions';
|
|
127
52
|
|
|
128
|
-
const
|
|
53
|
+
const Stack = Transition.createTransitionableStackNavigator();
|
|
129
54
|
|
|
130
55
|
export default function App() {
|
|
131
56
|
return (
|
|
132
57
|
<GestureHandlerRootView>
|
|
133
|
-
<
|
|
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>
|
|
134
76
|
</GestureHandlerRootView>
|
|
135
77
|
);
|
|
136
78
|
}
|
|
137
79
|
```
|
|
138
80
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
### Predefined Presets
|
|
142
|
-
Use these out-of-the-box animations via `Transition.presets`:
|
|
81
|
+
### 2. Expo Router
|
|
82
|
+
Use the withLayoutContext to convert it into an expo router compatible navigator.
|
|
143
83
|
|
|
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
84
|
```tsx
|
|
152
|
-
|
|
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);
|
|
153
99
|
```
|
|
154
100
|
|
|
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.
|
|
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.
|
|
101
|
+
Use it exactly like any other Expo Router layout:
|
|
160
102
|
|
|
161
103
|
```tsx
|
|
162
|
-
|
|
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";
|
|
104
|
+
import { Stack } from './layouts/stack.tsx'
|
|
167
105
|
|
|
168
|
-
export default function RootLayout()
|
|
169
|
-
return
|
|
106
|
+
export default function RootLayout(){
|
|
107
|
+
return(
|
|
170
108
|
<GestureHandlerRootView>
|
|
171
109
|
<Stack>
|
|
172
110
|
<Stack.Screen
|
|
173
111
|
name="a"
|
|
174
|
-
|
|
112
|
+
options={{
|
|
113
|
+
// You usually don't want your first screen to be a transparent modal.
|
|
114
|
+
skipDefaultScreenOptions: true,
|
|
115
|
+
}}
|
|
175
116
|
/>
|
|
176
117
|
<Stack.Screen
|
|
177
118
|
name="b"
|
|
178
|
-
options={
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
}
|
|
119
|
+
options={{
|
|
120
|
+
...Transition.presets.SlideFromTop(),
|
|
121
|
+
}}
|
|
221
122
|
/>
|
|
222
123
|
</Stack>
|
|
223
124
|
</GestureHandlerRootView>
|
|
224
|
-
)
|
|
125
|
+
)
|
|
225
126
|
}
|
|
226
127
|
```
|
|
227
128
|
|
|
228
|
-
|
|
129
|
+
> **Note**: `Transition.createTransitionableStackNavigator()` returns a Native Stack Navigator that's been injected with the necessary functionality for screen transitions to work.
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
## Creating your screen animations
|
|
133
|
+
|
|
134
|
+
### Using presets
|
|
135
|
+
|
|
136
|
+
Pick a built-in preset and spread it into the screen’s options.
|
|
137
|
+
The incoming screen automatically controls the previous screen.
|
|
229
138
|
|
|
230
139
|
```tsx
|
|
231
|
-
|
|
232
|
-
|
|
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>
|
|
161
|
+
```
|
|
233
162
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
{/* Your content */}
|
|
238
|
-
</Transition.View>
|
|
239
|
-
);
|
|
240
|
-
}
|
|
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:
|
|
241
166
|
|
|
242
|
-
|
|
243
|
-
|
|
167
|
+
```tsx
|
|
168
|
+
// a.tsx
|
|
169
|
+
<Transition.View>
|
|
170
|
+
...
|
|
171
|
+
</Transition.View>
|
|
244
172
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
</Transition.View>
|
|
250
|
-
);
|
|
251
|
-
}
|
|
173
|
+
// b.tsx
|
|
174
|
+
<Transition.View>
|
|
175
|
+
...
|
|
176
|
+
</Transition.View>
|
|
252
177
|
```
|
|
253
178
|
|
|
254
|
-
|
|
255
|
-
|
|
179
|
+
Without this wrapper, the transition system cannot animate the screen and the animation will appear broken or skipped.
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
### Navigator-level custom animations
|
|
184
|
+
|
|
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.
|
|
256
187
|
|
|
257
188
|
```tsx
|
|
258
|
-
|
|
259
|
-
<Stack.Screen
|
|
260
|
-
name="a"
|
|
261
|
-
listeners={Transition.createConfig}
|
|
262
|
-
/>
|
|
189
|
+
import { interpolate } from 'react-native-reanimated'
|
|
263
190
|
<Stack.Screen
|
|
264
191
|
name="b"
|
|
265
|
-
options={
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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 }],
|
|
273
206
|
},
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
207
|
+
};
|
|
208
|
+
},
|
|
209
|
+
transitionSpec: {
|
|
210
|
+
close: Transition.specs.DefaultSpec,
|
|
211
|
+
open: Transition.specs.DefaultSpec,
|
|
212
|
+
},
|
|
213
|
+
}}
|
|
282
214
|
/>
|
|
283
215
|
```
|
|
284
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
|
+
|
|
285
223
|
```tsx
|
|
286
|
-
// a.tsx (previous screen)
|
|
287
224
|
import { useScreenAnimation } from 'react-native-screen-transitions';
|
|
288
225
|
import Animated, { useAnimatedStyle, interpolate } from 'react-native-reanimated';
|
|
289
226
|
|
|
290
|
-
export default function
|
|
291
|
-
const {
|
|
227
|
+
export default function BScreen() {
|
|
228
|
+
const { current } = useScreenAnimation();
|
|
292
229
|
|
|
293
230
|
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
231
|
return {
|
|
297
|
-
|
|
232
|
+
opacity: current.progress.value
|
|
298
233
|
};
|
|
299
234
|
});
|
|
300
235
|
|
|
301
236
|
return (
|
|
302
|
-
<Animated.View style={animatedStyle}>
|
|
237
|
+
<Animated.View style={[{ flex: 1 }, animatedStyle]}>
|
|
303
238
|
{/* Your content */}
|
|
304
239
|
</Animated.View>
|
|
305
240
|
);
|
|
306
241
|
}
|
|
242
|
+
```
|
|
307
243
|
|
|
308
|
-
|
|
309
|
-
import { useScreenAnimation } from 'react-native-screen-transitions';
|
|
310
|
-
import Animated, { useAnimatedStyle, interpolate } from 'react-native-reanimated';
|
|
244
|
+
## Apply screen transitions to nested navigators
|
|
311
245
|
|
|
312
|
-
|
|
313
|
-
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`.
|
|
314
247
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
transform: [{ translateX }],
|
|
320
|
-
};
|
|
321
|
-
});
|
|
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';
|
|
322
252
|
|
|
253
|
+
export default function BLayout() {
|
|
323
254
|
return (
|
|
324
|
-
<
|
|
325
|
-
|
|
326
|
-
|
|
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>
|
|
327
261
|
);
|
|
328
262
|
}
|
|
263
|
+
```
|
|
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.
|
|
266
|
+
|
|
267
|
+
## Swipe-to-dismiss with scrollables
|
|
268
|
+
|
|
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:
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
import Transition from 'react-native-screen-transitions';
|
|
274
|
+
import { LegendList } from "@legendapp/list"
|
|
275
|
+
import { FlashList } from "@shopify/flash-list";
|
|
276
|
+
|
|
277
|
+
// Drop-in replacements
|
|
278
|
+
const ScrollView = Transition.ScrollView;
|
|
279
|
+
const FlatList = Transition.FlatList;
|
|
329
280
|
|
|
281
|
+
// Or wrap any list you like
|
|
282
|
+
const TransitionFlashList =
|
|
283
|
+
Transition.createTransitionAwareScrollable(FlashList);
|
|
284
|
+
|
|
285
|
+
const TransitionLegendList =
|
|
286
|
+
Transition.createTransitionAwareScrollable(LegendList);
|
|
287
|
+
```
|
|
330
288
|
|
|
289
|
+
Enable the gesture on the screen:
|
|
290
|
+
|
|
291
|
+
```tsx
|
|
292
|
+
<Stack.Screen
|
|
293
|
+
name="gallery"
|
|
294
|
+
options={{
|
|
295
|
+
gestureEnabled: true,
|
|
296
|
+
gestureDirection: 'vertical', // or 'horizontal', ['vertical', 'horizontal'], etc.
|
|
297
|
+
}}
|
|
298
|
+
/>
|
|
331
299
|
```
|
|
332
300
|
|
|
301
|
+
Use it in the screen:
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
export default function B() {
|
|
305
|
+
return (
|
|
306
|
+
<Transition.ScrollView>
|
|
307
|
+
{/* content */}
|
|
308
|
+
</Transition.ScrollView>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Gesture rules (handled automatically):
|
|
314
|
+
|
|
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
|
|
318
|
+
|
|
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.
|
|
321
|
+
|
|
322
|
+
## Performance tips
|
|
323
|
+
|
|
324
|
+
Keep animations smooth and responsive:
|
|
325
|
+
|
|
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
|
|
329
|
+
|
|
330
|
+
## Roadmap
|
|
331
|
+
|
|
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
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
## Support and Development
|
|
333
338
|
|
|
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.
|
|
339
|
+
This package is provided as-is and is developed in my free time. While I strive to maintain and improve it, please understand that:
|
|
339
340
|
|
|
341
|
+
- **Updates and bug fixes** may take time to implement
|
|
342
|
+
- **Feature requests** will be considered but may not be prioritized immediately
|
|
340
343
|
|
|
341
|
-
|
|
344
|
+
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.
|
|
342
345
|
|
|
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
346
|
|
|
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
347
|
|
|
352
|
-
See the examples in `/examples/expo-router-example` and `/examples/react-nav-example` for full demos.
|
|
353
348
|
|
|
354
349
|
## License
|
|
355
350
|
MIT
|