I cannot find how to do a 3D carousel (aka slideshow) with React being able to show at least three elements. There seems not to be up-to-date libraries or components for that in npm :(
Here is what it should look like:
After experimenting for a while, here is how I manage to do it using framer motion:
import './styles.css';
import { AnimatePresence, motion } from 'framer-motion';
import { useState } from 'react';
export default function App() {
const [[activeIndex, direction], setActiveIndex] = useState([0, 0]);
const items = ['🍔', '🍕', '🌭', '🍗'];
// we want the scope to be always to be in the scope of the array so that the carousel is endless
const indexInArrayScope =
((activeIndex % items.length) + items.length) % items.length;
// so that the carousel is endless, we need to repeat the items twice
// then, we slice the the array so that we only have 3 items visible at the same time
const visibleItems = [...items, ...items].slice(
indexInArrayScope,
indexInArrayScope + 3
);
const handleClick = newDirection => {
setActiveIndex(prevIndex => [prevIndex[0] + newDirection, newDirection]);
};
return (
<div className="main-wrapper">
<div className="wrapper">
{/*AnimatePresence is necessary to show the items after they are deleted because only max. 3 are shown*/}
<AnimatePresence mode="popLayout" initial={false}>
{visibleItems.map((item) => {
// The layout prop makes the elements change its position as soon as a new one is added
// The key tells framer-motion that the elements changed its position
return (
<motion.div
className="card"
key={item}
layout
custom={{
direction,
position: () => {
if (item === visibleItems[0]) {
return 'left';
} else if (item === visibleItems[1]) {
return 'center';
} else {
return 'right';
}
},
}}
variants={variants}
initial="enter"
animate="center"
exit="exit"
transition={{ duration: 1 }}
>
{item}
</motion.div>
);
})}
</AnimatePresence>
</div>
<div className="buttons">
<motion.button
whileTap={{ scale: 0.8 }}
onClick={() => handleClick(-1)}
>
◀︎
</motion.button>
<motion.button whileTap={{ scale: 0.8 }} onClick={() => handleClick(1)}>
▶︎
</motion.button>
</div>
</div>
);
}
const variants = {
enter: ({ direction }) => {
return { scale: 0.2, x: direction < 1 ? 50 : -50, opacity: 0 };
},
center: ({ position }) => {
return {
scale: position() === 'center' ? 1 : 0.7,
x: 0,
zIndex: zIndex[position()],
opacity: 1,
};
},
exit: ({ direction }) => {
return { scale: 0.2, x: direction < 1 ? -50 : 50, opacity: 0 };
},
};
const zIndex = {
left: 1,
center: 2,
right: 1,
};
Here is a code sandbox with the solution: https://codesandbox.io/s/react-3d-carousel-wth-framer-motion-rtn6vx?file=/src/App.js