This demo has several versions:
scroll-timeline
ScrollTimeline
This demo features three content columns. The central column follows regular scroll, yet the two surrounding ones scroll in the opposite direction.
As for the basic layout, there is one .columns wrapper containing three nested .column elements. The columns that should reverse scroll, get the class .column-reverse added as well.
.columns
.column
.column-reverse
<div class="columns"> <div class="column column-reverse">…</div> <div class="column">…</div> <div class="column column-reverse">…</div> </div>
The basic layout is done using CSS Grid, and inside each .column, the contained .column__item elements are positioned using CSS Flexbox
.column__item
/* Three column layout */ .columns { display: grid; grid-template-columns: repeat(3, 1fr); } .column { display: flex; flex-direction: column; }
In browsers that support CSS Scroll-driven animations, the .column-reverse columns their starting position is set to -100% + 100vh. That way their bottom edge touches the bottom edge of the viewport. The end position of the animation is a translation in the opposite direction: 100% down, but minus 100vh to keep it on-screen.
-100% + 100vh
100%
100vh
To prevent the wrapping .columns from growing by doing that translation, its content is prevented from bleeding out of it using the overflow property.
overflow
Finally, as the columns will be scrolling in a reversed direction, the order for the items inside each column needs to be reversed as well. That way the first item will be at the bottom of the reversed column.
The animation is attached to a ScrollTimeline that tracks the root scroller.
// As we're about to shift content out of .columns, we need it to hide its overflow document.querySelector(".columns").style.overflowY = "hidden"; // Set up timeline const timeline = new ScrollTimeline({ source: document.documentElement, }); // Loop all eligible columns document.querySelectorAll(".column-reverse").forEach(($column) => { // Flip item order in reverse columns $column.style.flexDirection = "column-reverse"; // Hook Animation $column.animate( { transform: [ "translateY(calc(-100% + 100vh))", "translateY(calc(100% - 100vh))" ] }, { fill: "both", timeline } ); });
â ī¸ Your browser does not support Scroll-driven Animations. Please use Chrome 115 or newer.