36. Stack 3D
Another advanced example.
Framer Motion
A few details:
- There are always just two cards, and their
key
s count up when the first one is removed. - The cards are wrapped in an
<AnimatePresence>
, and the first card will have anexit
that animates it to the left or right (starting from the point where you released it). - Each card has
scale
androtate
motion values that are transformed by the card’sx
. - The actual
x
value of theexit
animation is set just before it happens, in thehandleDragEnd()
handler that gets called ononDragEnd()
. - The parent’s
setIndex()
is passed to the first card, and when it is called (in that samehandleDragEnd()
), the cards change position.
function Card(props) {
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,
})
function handleDragEnd(event, info) {
if (info.offset.x < -100) {
props.setExitX(-250)
props.setIndex(props.index + 1)
}
if (info.offset.x > 100) {
props.setExitX(250)
props.setIndex(props.index + 1)
}
}
return (
<motion.div
style={{
width: 150,
height: 150,
position: "absolute",
top: 0,
x: x,
rotate: rotate,
cursor: "grab",
}}
whileTap={{ cursor: "grabbing" }}
drag={props.drag}
dragConstraints={{
top: 0,
right: 0,
bottom: 0,
left: 0,
}}
onDragEnd={handleDragEnd}
initial={props.initial}
animate={props.animate}
transition={props.transition}
exit={{
x: props.exitX,
opacity: 0,
scale: 0.5,
transition: { duration: 0.2 },
}}
>
<motion.div
style={{
width: 150,
height: 150,
backgroundColor: "#fff",
borderRadius: 30,
scale: scale,
}}
/>
</motion.div>
)
}
export function FM36Stack3D() {
const [index, setIndex] = React.useState(0)
const [exitX, setExitX] = React.useState("100%")
return (
<Center>
<motion.div
style={{
width: 150,
height: 150,
position: "relative",
}}
>
<AnimatePresence initial={false}>
<Card
key={index + 1}
initial={{
scale: 0,
y: 105,
opacity: 0,
}}
animate={{
scale: 0.75,
y: 30,
opacity: 0.5,
}}
transition={{
scale: { duration: 0.2 },
opacity: { duration: 0.4 },
}}
/>
<Card
key={index}
animate={{
scale: 1,
y: 0,
opacity: 1,
}}
transition={{
type: "spring",
stiffness: 300,
damping: 20,
opacity: {
duration: 0.2,
},
}}
exitX={exitX}
setExitX={setExitX}
index={index}
setIndex={setIndex}
drag="x"
/>
</AnimatePresence>
</motion.div>
</Center>
)
}
Code component
Frames instead of motion divs, but otherwise the same.
function Card(props) {
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,
})
function handleDragEnd(event, info) {
if (info.offset.x < -100) {
props.setExitX(-250)
props.setIndex(props.index + 1)
}
if (info.offset.x > 100) {
props.setExitX(250)
props.setIndex(props.index + 1)
}
}
return (
<Frame
// Visual & layout
size={150}
backgroundColor="transparent"
// Dragging
drag={props.drag}
dragConstraints={{
top: 0,
right: 0,
bottom: 0,
left: 0,
}}
onDragEnd={handleDragEnd}
// Transformation
x={x}
rotate={rotate}
// Animation
initial={props.initial}
animate={props.animate}
transition={props.transition}
// Animate presence
exit={{
x: props.exitX,
opacity: 0,
scale: 0.5,
transition: { duration: 0.2 },
}}
>
<Frame
// Visual & layout
size={150}
radius={30}
backgroundColor="#fff"
// Transformation
scale={scale}
/>
</Frame>
)
}
export function CC36Stack3D() {
const [index, setIndex] = React.useState(0)
const [exitX, setExitX] = React.useState("100%")
return (
<Frame size={150} backgroundColor="transparent" center>
<AnimatePresence initial={false}>
<Card
key={index + 1}
initial={{
scale: 0,
y: 105,
opacity: 0,
}}
animate={{
scale: 0.75,
y: 30,
opacity: 0.5,
}}
transition={{
scale: { duration: 0.2 },
opacity: { duration: 0.4 },
}}
/>
<Card
key={index}
animate={{
scale: 1,
y: 0,
opacity: 1,
}}
transition={{
type: "spring",
stiffness: 300,
damping: 20,
opacity: {
duration: 0.2,
},
}}
exitX={exitX}
setExitX={setExitX}
index={index}
setIndex={setIndex}
drag="x"
/>
</AnimatePresence>
</Frame>
)
}
Override
This might be possible, probably with three cards that get reused continuously (and no <AnimatePresence>
). But why bother? A component version will always be easier to make.
2 comments on “36. Stack 3D”
Leave a Reply
You must be logged in to post a comment.
After dragging out the top card, is both cards’ key changed from 0, 1 to 1, 2? I think I get the idea of what is going when dragging the top card, but after drag, why is the bottom card animating to the same size as top card? Are you planning doing a break down for this example soon?
I didn’t make this one, and I also had to take a good look to figure out what’s happening (I only did the code component version).
Yes, the
index
(and therefore the cards’ keys) always counts up.I suppose the bottom card animates to the front because it becomes that card by virtue of its
key
.Let’s say the bottom card at some point has a
key
of6
, and the front one akey
of7
. When theindex
counts up, the bottom card becomes the one with akey
of7
and, therefore, kind of ‘assumes that position’.