Custom Animations
v5 has two complementary animation surfaces:
itemAnimationcontrols the outer style of every slide;- render-item
relativeProgresslets content inside a slide animate independently.
Both are UI-thread/worklet surfaces. layout and itemAnimation are mutually exclusive.
itemAnimation
import { interpolate } from "react-native-reanimated";
import {
Carousel,
type CarouselItemAnimation,
} from "react-native-reanimated-carousel";
const itemAnimation: CarouselItemAnimation = (relativeProgress) => {
"worklet";
return {
opacity: interpolate(relativeProgress, [-1, 0, 1], [0.4, 1, 0.4]),
transform: [
{
scale: interpolate(relativeProgress, [-1, 0, 1], [0.85, 1, 0.85]),
},
],
};
};
<Carousel
data={data}
renderItem={({ item }) => <Slide item={item} />}
itemAnimation={itemAnimation}
/>;The callback signature is:
type CarouselItemAnimation = (
relativeProgress: number,
index: number
) => ViewStyle;It must be worklet-compatible. Avoid JS-only APIs, React state, and side effects inside it.
Relative progress semantics
For each item:
0means selected;- a positive value means logically forward;
- a negative value means logically backward;
- fractions represent movement between items;
- values are not clamped;
- loop mode uses the nearest equivalent copy and remains continuous.
The sign is the same for horizontal, vertical, LTR, and RTL carousels. This makes opacity, scale, and logical sequencing portable across directions.
Animating content inside a slide
renderItem receives a SharedValue<number>:
import Animated, {
interpolate,
useAnimatedStyle,
} from "react-native-reanimated";
import type { SharedValue } from "react-native-reanimated";
function AnimatedCaption({
relativeProgress,
title,
}: {
relativeProgress: SharedValue<number>;
title: string;
}) {
const style = useAnimatedStyle(() => ({
opacity: interpolate(
Math.abs(relativeProgress.value),
[0, 1],
[1, 0]
),
transform: [
{
translateY: interpolate(
relativeProgress.value,
[-1, 0, 1],
[24, 0, -24]
),
},
],
}));
return <Animated.Text style={style}>{title}</Animated.Text>;
}
<Carousel
data={data}
renderItem={({ item, relativeProgress }) => (
<AnimatedCaption title={item.title} relativeProgress={relativeProgress} />
)}
/>;Do not read relativeProgress.value during React render. Read it from a Reanimated worklet such as useAnimatedStyle or useDerivedValue.
Horizontal RTL
Logical progress is normalized, but React Native transforms are physical. If a custom effect moves items on the x-axis, map its sign for RTL:
import { I18nManager } from "react-native";
import { interpolate } from "react-native-reanimated";
import type { CarouselItemAnimation } from "react-native-reanimated-carousel";
const rtlSign = I18nManager.isRTL ? -1 : 1;
const itemAnimation: CarouselItemAnimation = (relativeProgress) => {
"worklet";
const logicalX = interpolate(
relativeProgress,
[-1, 0, 1],
[-80, 0, 80]
);
return {
transform: [{ translateX: logicalX * rtlSign }],
};
};Built-in normal, parallax, and horizontal-stack layouts perform this mapping internally. Do not reverse data or mirror the whole carousel with scaleX.
Vertical effects do not depend on RTL.
Animation sanitization
Carousel sanitizes the returned style before applying it:
- invalid or non-finite style values are removed;
zIndexis normalized to a finite integer;- item dimensions remain owned by Carousel.
Use finite interpolation output and keep expensive calculations outside the callback when they can be precomputed.
Transition animation
itemAnimation describes slide appearance. The separate animation prop describes how the carousel moves to a settled position:
<Carousel
data={data}
renderItem={renderItem}
itemAnimation={itemAnimation}
animation={{ type: "timing", duration: 350 }}
/>The transition configuration applies to gesture snapping, imperative commands, and autoplay. For a spring:
<Carousel
data={data}
renderItem={renderItem}
itemAnimation={itemAnimation}
animation={{
type: "spring",
damping: 18,
stiffness: 180,
}}
/>Reanimated applies the operating system's reduce-motion preference by default.
Built-in layout or custom animation
Use a built-in layout when it covers the effect:
<Carousel
data={data}
renderItem={renderItem}
layout={{ type: "parallax", offset: 80, scale: 0.9 }}
/>Use itemAnimation when the slide style needs a custom mapping. Passing both is a TypeScript error and a runtime error for untyped JavaScript.