Skip to content

Instantly share code, notes, and snippets.

@entity
Last active March 29, 2026 18:36
Show Gist options
  • Select an option

  • Save entity/4e0b0f6fb32b5866deaef2ae8d27828f to your computer and use it in GitHub Desktop.

Select an option

Save entity/4e0b0f6fb32b5866deaef2ae8d27828f to your computer and use it in GitHub Desktop.
3D Carousel with CSS scroll-driven animations + CSS variable fallback
/* Destinations carousel — fallback: all transforms derived from a single --si CSS variable */
.carousel-card {
--raw: calc(var(--i) - var(--si));
--pos: calc(mod(var(--raw) + var(--half-n), var(--n)) - var(--half-n));
--abs: abs(var(--pos));
--sgn: sign(var(--pos));
/* scale: 1 → 0.8 → 0.7 → 0.6 (piecewise linear) */
--s: max(0.6, max(1 - 0.2 * var(--abs), 0.9 - 0.1 * var(--abs)));
/* rotateY: linear cap at 25deg */
--ry: calc(var(--sgn) * -1deg * min(25, var(--abs) * 21));
/* z-index: 30 → 20 → 10 → 5 */
--z: round(max(5, max(30 - 10 * var(--abs), 20 - 5 * var(--abs))), 1);
/* opacity: 1 → 1 → 0.6 → 0 */
--co: clamp(0, min(1, min(1.4 - 0.4 * var(--abs), 1.8 - 0.6 * var(--abs))), 1);
/* translateY: 0 → 20 → 40 → 50 (cards drop as they move from center) */
--ty: calc(1px * min(50, min(var(--abs) * 20, 20 + var(--abs) * 10)));
transform: perspective(900px) scale(var(--s)) rotateY(var(--ry)) translateY(var(--ty));
z-index: var(--z);
opacity: var(--co);
pointer-events: none;
&[data-visible] {
pointer-events: auto;
}
}
/* Destinations carousel — scroll-driven animation (compositor thread, no JS per frame) */
@supports (animation-timeline: view()) {
.carousel-card {
animation: carousel-3d linear both;
animation-timeline: view(inline);
transform: none;
z-index: auto;
opacity: 1;
}
@keyframes carousel-3d {
0%, 18% {
transform: perspective(900px) scale(0.6) rotateY(-15deg) translateY(50px);
opacity: 0;
z-index: 5;
}
29% {
transform: perspective(900px) scale(0.75) rotateY(-15deg) translateY(25px);
opacity: 0.6;
z-index: 10;
}
39% {
transform: perspective(900px) scale(0.85) rotateY(-12deg) translateY(15px);
opacity: 1;
z-index: 20;
}
50% {
transform: perspective(900px) scale(1) rotateY(0deg) translateY(0px);
opacity: 1;
z-index: 30;
}
61% {
transform: perspective(900px) scale(0.85) rotateY(12deg) translateY(15px);
opacity: 1;
z-index: 20;
}
71% {
transform: perspective(900px) scale(0.75) rotateY(15deg) translateY(25px);
opacity: 0.6;
z-index: 10;
}
82%, 100% {
transform: perspective(900px) scale(0.6) rotateY(15deg) translateY(50px);
opacity: 0;
z-index: 5;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment