Some Examples » 32. Animate Presence: Stack 3D
32. Animate Presence: Stack 3D
This is quite a complicated example. A lot is going on here.
Code component
A few details:
- There are always just two cards whose
key
s count up when the first card is removed (changing the key triggers the Animate Presence animation). - The cards have
scale
androtate
Motion values that are transformed by the card’sx
position (when the card is draggable, only the first card is). - The cards are wrapped in an
<AnimatePresence>
, and the first card will have anexit
animation that moves it to the left or right (starting from the point where you release it). - The animations are passed in variants, of which there are two sets:
variantsFrontCard
andvariantsBackCard
. Theexit
variant of the front card uses acustom
property set on the card: thex
position it should animate to. - That
custom
value is saved in anexitX
state and gets set just before theexit
animation happens in thehandleDragEnd()
handler that is called ononDragEnd()
. - The parent’s
setIndex()
is passed to the first card, and when it is called (in that samehandleDragEnd()
), the cards change position.
This is the Card()
component:
function Card(props) {
const [exitX, setExitX] = useState(0)
const x = useMotionValue(0)
const scale = useTransform(x, [-150, 0, 150], [0.5, 1, 0.5])
const rotate = useTransform(x, [-150, 0, 150], [-45, 0, 45], {
clamp: false,
})
const variantsFrontCard = {
animate: { scale: 1, y: 0, opacity: 1 },
exit: (custom) => ({ x: custom, opacity: 0, scale: 0.5 }),
}
const variantsBackCard = {
initial: { scale: 0, y: 105, opacity: 0 },
animate: { scale: 0.75, y: 30, opacity: 0.5 },
}
function handleDragEnd(_, info) {
if (info.offset.x < -100) {
setExitX(-250)
props.setIndex(props.index + 1)
}
if (info.offset.x > 100) {
setExitX(250)
props.setIndex(props.index + 1)
}
}
return (
<motion.div
style={{
width: 150,
height: 150,
position: "absolute",
top: 0,
x,
rotate,
}}
drag={props.drag}
dragConstraints={{ top: 0, right: 0, bottom: 0, left: 0 }}
onDragEnd={handleDragEnd}
variants={props.frontCard ? variantsFrontCard : variantsBackCard}
initial="initial"
animate="animate"
exit="exit"
custom={exitX}
transition={
props.frontCard
? { type: "spring", stiffness: 300, damping: 20 }
: { scale: { duration: 0.2 }, opacity: { duration: 0.4 } }
}
>
<motion.div
style={{
width: 150,
height: 150,
backgroundColor: "#fff",
borderRadius: 30,
scale,
}}
/>
</motion.div>
)
}
And this is the main component that contains the two cards.
export default function CC_32_Animate_Presence_Stack_3D(props) {
const [index, setIndex] = useState(0)
return (
<div>
<motion.div
style={{ width: 150, height: 150, position: "relative" }}
>
<AnimatePresence initial={false}>
<Card key={index + 1} frontCard={false} />
<Card
key={index}
frontCard={true}
index={index}
setIndex={setIndex}
drag="x"
/>
</AnimatePresence>
</motion.div>
</div>
)
}
Code override
This might be possible with three cards that get reused continuously (and no <AnimatePresence>
). But why bother? A component version will always be easier to make.