0
# Event Handling
1
2
Optimized handlers for user interactions, scrolling, device events, and other reactive patterns that run efficiently on the UI thread.
3
4
## Capabilities
5
6
### Scroll Event Handling
7
8
High-performance scroll event handlers that can update shared values on the UI thread.
9
10
```typescript { .api }
11
/**
12
* Creates an optimized scroll event handler that runs on the UI thread
13
* @param handler - Scroll handler function or configuration object
14
* @returns Processed scroll handler for use with ScrollView components
15
*/
16
function useAnimatedScrollHandler<T>(
17
handler: ScrollHandler<T> | ScrollHandlers<T>
18
): ScrollHandlerProcessed<T>;
19
20
type ScrollHandler<T> = (event: ScrollEvent, context?: T) => void;
21
22
interface ScrollHandlers<T> {
23
onScroll?: ScrollHandler<T>;
24
onBeginDrag?: ScrollHandler<T>;
25
onEndDrag?: ScrollHandler<T>;
26
onMomentumBegin?: ScrollHandler<T>;
27
onMomentumEnd?: ScrollHandler<T>;
28
}
29
30
interface ScrollEvent {
31
contentOffset: { x: number; y: number };
32
contentSize: { width: number; height: number };
33
layoutMeasurement: { width: number; height: number };
34
targetContentOffset?: { x: number; y: number };
35
velocity?: { x: number; y: number };
36
zoomScale?: number;
37
}
38
```
39
40
**Usage Examples:**
41
42
```typescript
43
import React from "react";
44
import Animated, {
45
useSharedValue,
46
useAnimatedScrollHandler,
47
useAnimatedStyle,
48
interpolate,
49
Extrapolation
50
} from "react-native-reanimated";
51
52
const ScrollHandlerExample = () => {
53
const scrollY = useSharedValue(0);
54
const headerHeight = 200;
55
56
// Simple scroll handler
57
const scrollHandler = useAnimatedScrollHandler({
58
onScroll: (event) => {
59
scrollY.value = event.contentOffset.y;
60
},
61
});
62
63
// Advanced scroll handler with multiple events
64
const advancedScrollHandler = useAnimatedScrollHandler({
65
onScroll: (event) => {
66
scrollY.value = event.contentOffset.y;
67
},
68
onBeginDrag: (event) => {
69
console.log("Started dragging");
70
},
71
onEndDrag: (event) => {
72
console.log("Stopped dragging");
73
},
74
onMomentumBegin: (event) => {
75
console.log("Momentum scroll started");
76
},
77
onMomentumEnd: (event) => {
78
console.log("Momentum scroll ended");
79
},
80
});
81
82
// Parallax header style
83
const headerStyle = useAnimatedStyle(() => ({
84
transform: [
85
{
86
translateY: interpolate(
87
scrollY.value,
88
[0, headerHeight],
89
[0, -headerHeight / 2],
90
Extrapolation.CLAMP
91
),
92
},
93
],
94
opacity: interpolate(
95
scrollY.value,
96
[0, headerHeight / 2, headerHeight],
97
[1, 0.5, 0],
98
Extrapolation.CLAMP
99
),
100
}));
101
102
return (
103
<>
104
{/* Parallax header */}
105
<Animated.View
106
style={[
107
{
108
position: "absolute",
109
top: 0,
110
left: 0,
111
right: 0,
112
height: headerHeight,
113
backgroundColor: "lightblue",
114
zIndex: 1,
115
},
116
headerStyle,
117
]}
118
>
119
<Animated.Text>Parallax Header</Animated.Text>
120
</Animated.View>
121
122
{/* Scrollable content */}
123
<Animated.ScrollView
124
onScroll={scrollHandler}
125
scrollEventThrottle={16}
126
style={{ paddingTop: headerHeight }}
127
>
128
{/* Content items */}
129
{Array.from({ length: 50 }).map((_, index) => (
130
<Animated.View
131
key={index}
132
style={{
133
height: 80,
134
backgroundColor: index % 2 ? "white" : "lightgray",
135
justifyContent: "center",
136
alignItems: "center",
137
}}
138
>
139
<Animated.Text>Item {index}</Animated.Text>
140
</Animated.View>
141
))}
142
</Animated.ScrollView>
143
</>
144
);
145
};
146
```
147
148
### Scroll Offset Tracking
149
150
Utilities for tracking scroll position with animated refs.
151
152
```typescript { .api }
153
/**
154
* Tracks scroll offset of a scrollable component
155
* @param scrollableRef - Animated ref to the scrollable component
156
* @returns SharedValue containing the current scroll offset
157
*/
158
function useScrollOffset(scrollableRef: AnimatedRef<any>): SharedValue<number>;
159
160
/**
161
* @deprecated Use useScrollOffset instead
162
*/
163
function useScrollViewOffset(scrollableRef: AnimatedRef<any>): SharedValue<number>;
164
```
165
166
**Usage Example:**
167
168
```typescript
169
import React, { useRef } from "react";
170
import Animated, {
171
useAnimatedRef,
172
useScrollOffset,
173
useAnimatedStyle,
174
interpolateColor
175
} from "react-native-reanimated";
176
177
const ScrollOffsetExample = () => {
178
const scrollRef = useAnimatedRef();
179
const scrollOffset = useScrollOffset(scrollRef);
180
181
// Background color changes based on scroll position
182
const backgroundStyle = useAnimatedStyle(() => ({
183
backgroundColor: interpolateColor(
184
scrollOffset.value,
185
[0, 300, 600],
186
["white", "lightblue", "darkblue"]
187
),
188
}));
189
190
return (
191
<Animated.View style={[{ flex: 1 }, backgroundStyle]}>
192
<Animated.ScrollView ref={scrollRef}>
193
{Array.from({ length: 100 }).map((_, index) => (
194
<Animated.View
195
key={index}
196
style={{
197
height: 60,
198
margin: 10,
199
backgroundColor: "rgba(255, 255, 255, 0.8)",
200
borderRadius: 10,
201
justifyContent: "center",
202
alignItems: "center",
203
}}
204
>
205
<Animated.Text>Item {index}</Animated.Text>
206
</Animated.View>
207
))}
208
</Animated.ScrollView>
209
</Animated.View>
210
);
211
};
212
```
213
214
### Animated Reactions
215
216
React to changes in shared values with custom side effects.
217
218
```typescript { .api }
219
/**
220
* Executes side effects in response to shared value changes
221
* @param prepare - Function to prepare/compute values (runs on UI thread)
222
* @param react - Function to handle changes (runs on JS thread)
223
* @param dependencies - Optional dependency array for optimization
224
*/
225
function useAnimatedReaction<T>(
226
prepare: () => T,
227
react: (prepared: T, previous: T | null) => void,
228
dependencies?: React.DependencyList
229
): void;
230
```
231
232
**Usage Examples:**
233
234
```typescript
235
import React, { useState } from "react";
236
import Animated, {
237
useSharedValue,
238
useAnimatedReaction,
239
useAnimatedStyle,
240
withSpring,
241
runOnJS
242
} from "react-native-reanimated";
243
244
const AnimatedReactionExample = () => {
245
const scale = useSharedValue(1);
246
const [message, setMessage] = useState("Normal size");
247
248
// React to scale changes
249
useAnimatedReaction(
250
() => scale.value > 1.5,
251
(isLarge, wasLarge) => {
252
if (isLarge !== wasLarge) {
253
runOnJS(setMessage)(isLarge ? "Large!" : "Normal size");
254
}
255
}
256
);
257
258
// React to multiple values
259
const rotation = useSharedValue(0);
260
const position = useSharedValue({ x: 0, y: 0 });
261
262
useAnimatedReaction(
263
() => ({
264
isRotated: Math.abs(rotation.value) > 45,
265
isOffCenter: Math.abs(position.value.x) > 50 || Math.abs(position.value.y) > 50,
266
}),
267
(current, previous) => {
268
if (current.isRotated && !previous?.isRotated) {
269
runOnJS(console.log)("Started rotating");
270
}
271
if (current.isOffCenter && !previous?.isOffCenter) {
272
runOnJS(console.log)("Moved off center");
273
}
274
}
275
);
276
277
const animatedStyle = useAnimatedStyle(() => ({
278
transform: [
279
{ scale: scale.value },
280
{ rotate: `${rotation.value}deg` },
281
{ translateX: position.value.x },
282
{ translateY: position.value.y },
283
],
284
}));
285
286
const handlePress = () => {
287
scale.value = withSpring(scale.value === 1 ? 2 : 1);
288
rotation.value = withSpring(rotation.value + 90);
289
position.value = withSpring({
290
x: Math.random() * 100 - 50,
291
y: Math.random() * 100 - 50,
292
});
293
};
294
295
return (
296
<>
297
<Animated.Text>{message}</Animated.Text>
298
<Animated.View
299
style={[
300
{
301
width: 100,
302
height: 100,
303
backgroundColor: "lightcoral",
304
borderRadius: 10,
305
},
306
animatedStyle,
307
]}
308
/>
309
<Button title="Animate" onPress={handlePress} />
310
</>
311
);
312
};
313
```
314
315
### Keyboard Handling
316
317
Track keyboard animations and adjust UI accordingly.
318
319
```typescript { .api }
320
/**
321
* Provides animated keyboard information
322
* @param options - Optional configuration for keyboard tracking
323
* @returns Animated keyboard information object
324
*/
325
function useAnimatedKeyboard(options?: AnimatedKeyboardOptions): AnimatedKeyboardInfo;
326
327
interface AnimatedKeyboardInfo {
328
/** Current keyboard height as SharedValue */
329
height: SharedValue<number>;
330
/** Keyboard state as SharedValue */
331
state: SharedValue<KeyboardState>;
332
}
333
334
interface AnimatedKeyboardOptions {
335
/** Whether to use safe area insets */
336
isStatusBarTranslucentAndroid?: boolean;
337
}
338
339
enum KeyboardState {
340
CLOSED = 0,
341
OPEN = 1,
342
CLOSING = 2,
343
OPENING = 3,
344
}
345
```
346
347
**Usage Example:**
348
349
```typescript
350
import React from "react";
351
import { TextInput } from "react-native";
352
import Animated, {
353
useAnimatedKeyboard,
354
useAnimatedStyle,
355
KeyboardState
356
} from "react-native-reanimated";
357
358
const KeyboardExample = () => {
359
const keyboard = useAnimatedKeyboard();
360
361
// Adjust container based on keyboard
362
const containerStyle = useAnimatedStyle(() => ({
363
paddingBottom: keyboard.height.value,
364
backgroundColor: keyboard.state.value === KeyboardState.OPEN ? "lightblue" : "white",
365
}));
366
367
// Animate input field
368
const inputStyle = useAnimatedStyle(() => ({
369
transform: [
370
{
371
translateY: keyboard.state.value === KeyboardState.OPENING ? -20 : 0,
372
},
373
],
374
}));
375
376
return (
377
<Animated.View style={[{ flex: 1, padding: 20 }, containerStyle]}>
378
<Animated.View style={inputStyle}>
379
<TextInput
380
placeholder="Type something..."
381
style={{
382
height: 40,
383
borderWidth: 1,
384
borderColor: "gray",
385
borderRadius: 5,
386
paddingHorizontal: 10,
387
}}
388
/>
389
</Animated.View>
390
</Animated.View>
391
);
392
};
393
```
394
395
### Sensor Handling
396
397
Access device sensors with animated values.
398
399
```typescript { .api }
400
/**
401
* Provides access to device sensors with animated values
402
* @param sensorType - Type of sensor to access
403
* @param config - Optional sensor configuration
404
* @returns Animated sensor data
405
*/
406
function useAnimatedSensor(
407
sensorType: SensorType,
408
config?: SensorConfig
409
): AnimatedSensor;
410
411
enum SensorType {
412
ACCELEROMETER = 1,
413
GYROSCOPE = 2,
414
GRAVITY = 3,
415
MAGNETIC_FIELD = 4,
416
ROTATION = 5,
417
}
418
419
interface SensorConfig {
420
/** Sensor update interval in milliseconds */
421
interval?: number;
422
/** Whether to adjust for device orientation */
423
adjustToInterfaceOrientation?: boolean;
424
/** Interface orientation to adjust for */
425
interfaceOrientation?: InterfaceOrientation;
426
/** iOS reference frame */
427
iosReferenceFrame?: IOSReferenceFrame;
428
}
429
430
interface AnimatedSensor {
431
sensor: SharedValue<Value3D | ValueRotation>;
432
unregister: () => void;
433
}
434
435
interface Value3D {
436
x: number;
437
y: number;
438
z: number;
439
interfaceOrientation: InterfaceOrientation;
440
}
441
442
interface ValueRotation {
443
qw: number;
444
qx: number;
445
qy: number;
446
qz: number;
447
yaw: number;
448
pitch: number;
449
roll: number;
450
interfaceOrientation: InterfaceOrientation;
451
}
452
```
453
454
**Usage Example:**
455
456
```typescript
457
import React, { useEffect } from "react";
458
import Animated, {
459
useAnimatedSensor,
460
useAnimatedStyle,
461
SensorType
462
} from "react-native-reanimated";
463
464
const SensorExample = () => {
465
const accelerometer = useAnimatedSensor(SensorType.ACCELEROMETER, {
466
interval: 100, // Update every 100ms
467
});
468
469
const gyroscope = useAnimatedSensor(SensorType.GYROSCOPE);
470
471
// Tilt effect based on accelerometer
472
const tiltStyle = useAnimatedStyle(() => {
473
const { x, y } = accelerometer.sensor.value;
474
return {
475
transform: [
476
{ rotateX: `${y * 45}deg` },
477
{ rotateY: `${-x * 45}deg` },
478
],
479
};
480
});
481
482
// Rotation based on gyroscope
483
const rotationStyle = useAnimatedStyle(() => {
484
const { z } = gyroscope.sensor.value;
485
return {
486
transform: [{ rotateZ: `${z * 180}deg` }],
487
};
488
});
489
490
useEffect(() => {
491
// Cleanup sensors on unmount
492
return () => {
493
accelerometer.unregister();
494
gyroscope.unregister();
495
};
496
}, []);
497
498
return (
499
<>
500
<Animated.Text>Tilt your device!</Animated.Text>
501
502
<Animated.View
503
style={[
504
{
505
width: 100,
506
height: 100,
507
backgroundColor: "lightgreen",
508
margin: 20,
509
borderRadius: 10,
510
},
511
tiltStyle,
512
]}
513
>
514
<Animated.Text>Tilt</Animated.Text>
515
</Animated.View>
516
517
<Animated.View
518
style={[
519
{
520
width: 100,
521
height: 100,
522
backgroundColor: "lightcoral",
523
margin: 20,
524
borderRadius: 10,
525
},
526
rotationStyle,
527
]}
528
>
529
<Animated.Text>Rotate</Animated.Text>
530
</Animated.View>
531
</>
532
);
533
};
534
```
535
536
### Generic Event Handling
537
538
Handle custom events and gestures with optimized performance.
539
540
```typescript { .api }
541
/**
542
* Creates a generic event handler that can run on the UI thread
543
* @param handler - Event handler function
544
* @param eventNames - Optional array of event names to handle
545
* @returns Processed event handler
546
*/
547
function useEvent<T>(
548
handler: EventHandler<T>,
549
eventNames?: string[]
550
): EventHandlerProcessed<T>;
551
552
type EventHandler<T> = (event: ReanimatedEvent<T>) => void;
553
554
interface ReanimatedEvent<T> {
555
eventName: string;
556
[key: string]: any;
557
}
558
```
559
560
### Accessibility Support
561
562
Check for reduced motion preferences for accessibility.
563
564
```typescript { .api }
565
/**
566
* Returns the current reduced motion preference
567
* @returns Boolean indicating if reduced motion is preferred
568
*/
569
function useReducedMotion(): SharedValue<boolean>;
570
```
571
572
**Usage Example:**
573
574
```typescript
575
import React from "react";
576
import Animated, {
577
useReducedMotion,
578
useSharedValue,
579
useAnimatedStyle,
580
withTiming,
581
withSpring
582
} from "react-native-reanimated";
583
import { Button } from "react-native";
584
585
const AccessibilityExample = () => {
586
const reducedMotion = useReducedMotion();
587
const scale = useSharedValue(1);
588
589
const animatedStyle = useAnimatedStyle(() => ({
590
transform: [{ scale: scale.value }],
591
}));
592
593
const handlePress = () => {
594
// Use different animation based on accessibility preference
595
if (reducedMotion.value) {
596
// Reduced motion: quick, simple animation
597
scale.value = withTiming(scale.value === 1 ? 1.1 : 1, { duration: 200 });
598
} else {
599
// Full motion: bouncy spring animation
600
scale.value = withSpring(scale.value === 1 ? 1.3 : 1);
601
}
602
};
603
604
return (
605
<>
606
<Animated.View
607
style={[
608
{
609
width: 100,
610
height: 100,
611
backgroundColor: "lightblue",
612
borderRadius: 10,
613
},
614
animatedStyle,
615
]}
616
>
617
<Animated.Text>Accessible Animation</Animated.Text>
618
</Animated.View>
619
620
<Button title="Animate" onPress={handlePress} />
621
</>
622
);
623
};
624
```
625
626
### Advanced Event Handling
627
628
Low-level event handling hooks for complex event composition and custom patterns.
629
630
```typescript { .api }
631
/**
632
* Creates a generic event handler for any native event
633
* @param handler - Function to handle the event
634
* @param eventNames - Array of event names to listen for
635
* @param rebuild - Whether to rebuild the handler on changes
636
* @returns Processed event handler
637
*/
638
function useEvent<Event extends object, Context = never>(
639
handler: EventHandler<Event, Context>,
640
eventNames?: readonly string[],
641
rebuild?: boolean
642
): EventHandlerProcessed<Event, Context>;
643
644
/**
645
* Composes multiple event handlers into a single handler
646
* @param handlers - Array of event handlers to compose
647
* @returns Composed event handler
648
*/
649
function useComposedEventHandler<Event extends object, Context = never>(
650
handlers: (EventHandlerProcessed<Event, Context> | null)[]
651
): EventHandlerProcessed<Event, Context>;
652
653
/**
654
* Low-level hook for creating reusable event handler logic
655
* @param handlers - Object of event handlers
656
* @param dependencies - Optional dependency array
657
* @returns Handler context with dependency information
658
*/
659
function useHandler<Event extends object, Context extends Record<string, unknown>>(
660
handlers: Record<string, ((event: ReanimatedEvent<Event>, context: Context) => void) | undefined>,
661
dependencies?: React.DependencyList
662
): UseHandlerContext<Context>;
663
664
type EventHandler<Event extends object, Context = never> =
665
(event: ReanimatedEvent<Event>, context?: Context) => void;
666
```
667
668
**Usage Examples:**
669
670
```typescript
671
// Generic event handling
672
const MyComponent = () => {
673
const handleTouch = useEvent((event) => {
674
'worklet';
675
console.log('Touch at:', event.x, event.y);
676
}, ['onTouchStart', 'onTouchMove']);
677
678
return <Animated.View onTouchStart={handleTouch} onTouchMove={handleTouch} />;
679
};
680
681
// Composing multiple handlers
682
const ComposedExample = () => {
683
const handler1 = useEvent((event) => {
684
'worklet';
685
console.log('Handler 1:', event.contentOffset.y);
686
});
687
688
const handler2 = useEvent((event) => {
689
'worklet';
690
console.log('Handler 2:', event.velocity?.y);
691
});
692
693
const composedHandler = useComposedEventHandler([handler1, handler2]);
694
695
return <Animated.ScrollView onScroll={composedHandler} />;
696
};
697
698
// Advanced handler patterns
699
const AdvancedExample = () => {
700
const { context, doDependenciesDiffer } = useHandler({
701
onScroll: (event, ctx) => {
702
'worklet';
703
ctx.lastScrollY = event.contentOffset.y;
704
},
705
onMomentumEnd: (event, ctx) => {
706
'worklet';
707
console.log('Scroll ended at:', ctx.lastScrollY);
708
},
709
}, []);
710
711
// Use the handler context for complex logic
712
useEffect(() => {
713
if (doDependenciesDiffer) {
714
console.log('Handler dependencies changed');
715
}
716
}, [doDependenciesDiffer]);
717
718
return <Animated.ScrollView /* handler setup */ />;
719
};
720
```
721
722
## Type Definitions
723
724
```typescript { .api }
725
type ScrollHandlerProcessed<T> = (event: ScrollEvent) => void;
726
727
type EventHandlerProcessed<T> = (event: ReanimatedEvent<T>) => void;
728
729
interface UseHandlerContext<T> {
730
context: SharedValue<T>;
731
doDependenciesDiffer: boolean;
732
useWeb: boolean;
733
}
734
735
enum InterfaceOrientation {
736
ROTATION_0 = 0,
737
ROTATION_90 = 90,
738
ROTATION_180 = 180,
739
ROTATION_270 = 270,
740
}
741
742
enum IOSReferenceFrame {
743
XArbitraryZVertical = "XArbitraryZVertical",
744
XArbitraryCorrectedZVertical = "XArbitraryCorrectedZVertical",
745
XMagneticNorthZVertical = "XMagneticNorthZVertical",
746
XTrueNorthZVertical = "XTrueNorthZVertical",
747
}
748
```