Great question! You’re very close, and your explanation is clear: you want all cards in the stack to smoothly "move forward" in Z (and maybe Y) as the top card animates away, so the stack feels dynamic and the peeled-away card ends up at the front (z=0) before flying off.
Right now, your animation for each card uses a static calculation for translateZ
and translateY
based on its index. But as the top card animates, the others stay stuck, so the stack doesn’t shift "forward" in Z space.
You want:
While card 1 animates up, cards 2–5 shift forward in Z (and maybe Y).
When card 2 animates, cards 3–5 shift forward, etc.
You need to update all cards’ Z/Y positions based on the scroll progress of the outgoing (top) card.
Key:
Track scroll progress per card (from 0 to 1).
For each card, interpolate its position based on:
How many cards above it have been animated.
How far the current outgoing card is in its animation.
Suppose you have N
cards.
For each card in stack, calculate:
Outgoing Card: animates as normal.
Other Cards:
js
// Imagine: progress is a number between 0 and 1 for the outgoing card (card 1)
cards.forEach((card, index) => {
let outgoingIdx = currentOutgoingCardIndex; // e.g., 0 for card-1, 1 for card-2
let progress = getScrollProgressForCard(outgoingIdx); // 0 to 1
if (index < outgoingIdx) {
// Already gone, maybe hidden or in final state
} else if (index === outgoingIdx) {
// Animate out (your current animation)
} else {
// These cards move "forward" as the outgoing card progresses
let z = -((index - outgoingIdx) * 40) + (progress * 40);
// Optionally also animate Y position if desired
card.style.transform = `translateZ(${z}px)`;
}
});
You’ll need to update the animation of all cards on scroll, maybe using a custom update
callback in animejs, or by controlling the transforms manually.
Create a timeline for each card swipe
On timeline progress, update all cards’ Z/Y positions based on progress.
Example for card-1 swipe:
js
const stackDepth = 40;
const cards = Array.from(document.querySelectorAll('.card'));
function updateStackPositions(outgoingIdx, progress) {
cards.forEach((card, idx) => {
if (idx <= outgoingIdx) return; // outgoing or already gone
// Move remaining cards forward in Z
let z = -((idx - outgoingIdx) * stackDepth) + (progress * stackDepth);
card.style.transform = `translateZ(${z}px)`;
});
}
// Animate outgoing card and stack on scroll
function animateCardSwipe(outgoingIdx) {
anime({
targets: cards[outgoingIdx],
// your animation props...
rotateX: [0, 25],
translateY: [outgoingIdx * 20, -window.innerHeight / 2 - 300],
// Animate stack
update: anim => {
let progress = anim.progress / 100; // 0 - 1
updateStackPositions(outgoingIdx, progress);
},
// trigger on scroll as you do
});
}
For each card swipe, animate outgoing card as you do.
For the rest of the stack, interpolate their Z (and Y) positions based on the outgoing card’s animation progress.
Use anime.js's update
callback or manually set transform
on each card as animation (or scroll) progresses.