react-parallax-carousel
v1.1.0
Published
A smooth spring-physics parallax carousel for React — cinematic card transitions with masked parallax images
Maintainers
Readme
react-parallax-carousel
A smooth, spring-physics parallax carousel for React. Cards act as overflow: hidden mask windows over oversized images that shift at a different speed — pure translateX parallax.
- Overdamped spring physics — cinematic glide
- Only the immediately adjacent cards peek in from each edge; all others stay hidden
- Asymmetric peek widths — right peek can be larger than left for a "what's next" hint
- Masked parallax: large image revealed by the moving card window
- Chevron arrows on peek cards for clear navigation affordance
- Click the active slide to follow a link
- Auto-play with configurable delay
- Loop / no-loop mode
- Pointer + touch + keyboard support
Install
npm install react-parallax-carouselReact 17+ is a peer dependency.
Usage
import { ParallaxCarousel } from 'react-parallax-carousel';
const slides = [
{
image: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1400',
tag: 'Landscape',
title: 'Alpine Silence',
href: 'https://en.wikipedia.org/wiki/Swiss_Alps',
},
{
image: 'https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=1400',
tag: 'Nature',
title: 'Into the Forest',
href: 'https://en.wikipedia.org/wiki/Forest',
},
{
image: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=1400',
tag: 'Coastal',
title: 'Ocean Drift',
href: 'https://en.wikipedia.org/wiki/Ocean',
},
];
function App() {
return (
<div style={{ background: '#111113', minHeight: '100vh', display: 'flex', alignItems: 'center' }}>
<ParallaxCarousel
slides={slides}
peekLeftVw={10}
peekRightVw={20}
title="Discover the World"
subtitle="Drag to explore"
loop={true}
autoPlay={true}
autoPlayDelay={4000}
showArrows={true}
linkTarget="_blank"
onChange={(index) => console.log('Active slide:', index)}
/>
</div>
);
}Layout Model
The viewport is divided into three regions that always add up to 100 vw:
┌─────────────┬──────────────────────────────────┬──────────────────┐
│ peekLeftVw │ active card (auto) │ peekRightVw │
│ (10 vw) │ 100 - left - right = 70 vw │ (20 vw) │
└─────────────┴──────────────────────────────────┴──────────────────┘- The previous card is
2 × peekLeftVwwide, so exactly half of it is visible on the left edge. - The next card is
2 × peekRightVwwide, so exactly half of it is visible on the right edge. - Every other card collapses to zero width and is never visible, no matter how many slides you have.
Slide Object
| Field | Type | Required | Description |
|---------|--------|----------|----------------------------------------------------------|
| image | string | ✓ | Image URL |
| title | string | | Card heading |
| tag | string | | Small uppercase label above the heading |
| alt | string | | Image alt text (falls back to title) |
| id | any | | Stable React key (falls back to array index) |
| href | string | | URL opened when the active slide is tapped/clicked |
Props
Layout
| Prop | Type | Default | Description |
|----------------|--------|----------|--------------------------------------------------------------------|
| slides | array | [] | Array of slide objects (see above) |
| peekLeftVw | number | 10 | Visible width of the left peek in vw. Previous card = 2× this |
| peekRightVw | number | 18 | Visible width of the right peek in vw. Next card = 2× this |
| cardHeight | string | '78vh' | CSS height of each card |
| gap | string | '20px' | Gap between cards (use px units) |
| borderRadius | string | '24px' | CSS border-radius of cards |
Header
| Prop | Type | Default | Description |
|--------------|---------|------------------------|-------------------------|
| title | string | 'Discover the World' | Header title text |
| subtitle | string | 'Drag to explore' | Header subtitle text |
| showHeader | boolean | true | Show or hide the header |
Navigation
| Prop | Type | Default | Description |
|----------------|---------|---------------------------|-------------------------------------------------|
| initialIndex | number | 0 | Starting slide index |
| loop | boolean | true | Wrap around at the first and last slide |
| showArrows | boolean | true | Show chevron arrows on the prev/next peek cards |
| arrowColor | string | 'rgba(255,255,255,0.9)' | Arrow icon color |
Links
| Prop | Type | Default | Description |
|--------------|--------|------------|----------------------------------------------------------------|
| linkTarget | string | '_blank' | Browser target for slide href links ('_self' for same tab) |
Auto-play
| Prop | Type | Default | Description |
|-----------------|---------|---------|----------------------------------------|
| autoPlay | boolean | false | Automatically advance slides |
| autoPlayDelay | number | 3000 | Delay between advances in milliseconds |
Physics
| Prop | Type | Default | Description |
|-----------------|--------|---------|---------------------------------------------------|
| parallaxRange | number | 14 | Image shift range in % (max safe ~18) |
| parallaxLerp | number | 0.1 | Image lag smoothing — lower = more luxurious drag |
| stiffness | number | 0.022 | Spring stiffness — lower = slower glide |
| damping | number | 0.58 | Spring damping — higher = less overshoot |
| maxVel | number | 12 | Max release velocity in px/frame |
| widthLerp | number | 0.07 | Card width animation speed |
Callbacks
| Prop | Type | Description |
|------------|----------|---------------------------------------------------|
| onChange | function | (index: number) => void — fires on slide change |
Behaviour Notes
Active slide link: tapping/clicking the active card opens slide.href in linkTarget. Tapping an inactive card navigates to it (does not follow its link).
Peek sizing: peekLeftVw and peekRightVw are the visible widths. The carousel automatically sizes peek cards at 2× those values so exactly half of each card is visible. Making peekRightVw larger than peekLeftVw hints at the upcoming slide:
// 10 vw left peek, 20 vw right peek — right shows twice as much
<ParallaxCarousel slides={slides} peekLeftVw={10} peekRightVw={20} />One card per side — always: cards beyond the immediate neighbours collapse to zero width with no gap contribution, so no matter how many slides you have, only one card ever peeks in from each edge.
Auto-play: pauses while the user is dragging and resets its timer on every manual navigation.
Loop: when loop={true} (default), navigating past the last slide wraps to the first, and vice versa — including via keyboard and auto-play.
Arrows: the left chevron appears centered on the previous peek card; the right chevron on the next. Both are purely visual — the whole card is the click target.
Slides changing length: animation state is initialised once on mount. If you change the slides array length, use the key prop to remount:
<ParallaxCarousel key={slides.length} slides={slides} />Styles: a single <style> tag is injected into document.head on first mount and shared across all instances. It's scoped to the .rpc-* class namespace and won't affect your app's global styles.
Keyboard: ArrowRight / ArrowDown advances, ArrowLeft / ArrowUp goes back.
Building from Source
npm install
npm run build # outputs dist/index.cjs + dist/index.esm.js
npm run dev # watch modeLicense
MIT
