Advanced
Stories
Build an Instagram Stories-inspired 3d effect.
.frame {
aspect-ratio: 9 / 16;
width: 100%;
max-width: 20rem;
perspective: 1200px;
overflow: clip;
}
.carousel {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 100%;
scroll-snap-type: x mandatory;
height: 100%;
}
.slide {
width: 100%;
height: 100%;
scroll-snap-align: center;
scroll-snap-stop: always;
perspective: 1200px;
view-timeline: --stories inline;
}
.slide .card {
width: 100%;
height: 100%;
animation: stories linear both;
animation-timeline: --stories;
animation-range: cover;
}
@keyframes stories {
0% {
transform: rotateY(90deg);
transform-origin: left center;
}
49% {
transform: rotateY(0deg);
transform-origin: left center;
}
50% {
transform: rotateY(0deg);
transform-origin: center center;
}
51% {
transform: rotateY(0deg);
transform-origin: right center;
}
100% {
transform: rotateY(-90deg);
transform-origin: right center;
}
}
<div class="frame">
<BlossomCarousel class="carousel">
<div class="slide">
<div class="card"></div>
</div>
<div class="slide">
<div class="card"></div>
</div>
<div class="slide">
<div class="card"></div>
</div>
...
</BlossomCarousel>
</div>
carousel.addEventListener("overscroll", (event: CustomEvent) => {
const overscroll = event.detail.left;
event.target.style.transformOrigin =
overscroll > 0 ? "left center" : "right center";
event.target.style.transform = `translateX(${overscroll}px) rotateY(${(overscroll / event.target.clientWidth) * 70}deg)`;
});
.slide {
view-timeline: --stories inline;
}
.slide .card {
animation: stories linear both;
animation-timeline: --stories;
animation-range: cover;
}
@keyframes stories {
0% {
transform: rotateY(90deg);
transform-origin: left center;
}
49% {
transform: rotateY(0deg);
transform-origin: left center;
}
50% {
transform: rotateY(0deg);
transform-origin: center center;
}
51% {
transform: rotateY(0deg);
transform-origin: right center;
}
100% {
transform: rotateY(-90deg);
transform-origin: right center;
}
}
<div class="aspect-[9/16] w-full max-w-80 perspective-normal overflow-clip">
<BlossomCarousel
class="grid! h-full snap-x snap-mandatory auto-cols-[100%] grid-flow-col"
>
<div class="slide perspective-normal size-full snap-center snap-always">
<div class="card size-full"></div>
</div>
<div class="slide perspective-normal size-full snap-center snap-always">
<div class="card size-full"></div>
</div>
<div class="slide perspective-normal size-full snap-center snap-always">
<div class="card size-full"></div>
</div>
...
</BlossomCarousel>
</div>
carousel.addEventListener("overscroll", (event: CustomEvent) => {
const overscroll = event.detail.left;
event.target.style.transformOrigin =
overscroll > 0 ? "left center" : "right center";
event.target.style.transform = `translateX(${overscroll}px) rotateY(${(overscroll / event.target.clientWidth) * 70}deg)`;
});