useAnimatedScrollHandler() is Reanimated 11,010 's version of Animated.event(), with one difference that matters: its handlers are worklets, so you can run any logic per scroll event on the UI thread, not just copy a number. It accepts onScroll, onBeginDrag, onEndDrag, onMomentumBegin and onMomentumEnd, and you pass the result to a Reanimated scroll component. This header shrinks by animating height, a layout prop that Animated's native driver refuses:
const top = useSafeAreaInsets().top;
const y = useSharedValue(0);
const onScroll = useAnimatedScrollHandler({
onScroll: (e) => { y.value = e.contentOffset.y; }, // UI thread, every event
onEndDrag: (e) => { scheduleOnRN(say, 'end drag at', Math.round(e.contentOffset.y)); },
onMomentumEnd: (e) => {
scheduleOnRN(say, 'momentum end at', Math.round(e.contentOffset.y));
},
});
const header = useAnimatedStyle(() => ({
height: top + interpolate(y.value, [0, 100], [120, 56], Extrapolation.CLAMP), // layout
backgroundColor: y.value > 100 ? '#0b1320' : '#1565c0',
}));
// ...header styles an Animated.View above <Animated.FlatList onScroll={onScroll} ...>Two drags with adb shell input swipe:
LOG end drag at 118 LOG momentum end at 118 LOG end drag at 318 LOG momentum end at 318
interpolate() and interpolateColor() are worklet versions of Interpolation's ideas. Animating height makes Yoga lay out the header and the list every frame: fine for one header, but prefer transforms for many views. useScrollOffset(animatedRef) returns the offset as a shared value without a handler.