Version selector

This demo has several versions:

  1. CSS scroll-timeline
  2. JavaScript WAAPI + ScrollTimeline

About this demo

This demo features three content columns. The central column follows regular scroll, yet the two surrounding ones scroll in the opposite direction.

The Code / Explanation

Base layout

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.

<div class="columns">
	<div class="column column-reverse">…</div>
	<div class="column">…</div>
	<div class="column column-reverse">…</div>

The basic layout is done using CSS Grid, and inside each .column, the contained .column__item elements are positioned using CSS Flexbox

/* Three column layout */
.columns {
	display: grid;
	grid-template-columns: repeat(3, 1fr);

.column {
	display: flex;
	flex-direction: column;

Scroll-driven animations

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.

To prevent the wrapping .columns from growing by doing that translation, its content is prevented from bleeding out of it using the overflow property.

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-reverse";

	// Hook Animation
			transform: [
				"translateY(calc(-100% + 100vh))",
				"translateY(calc(100% - 100vh))"
			fill: "both",

⚠ī¸ Your browser does not support Scroll-driven Animations. Please use Chrome 115 or newer.

Cyber Blue 2011
Gnostic Will 2012
French Kiss 2013
Half Life 2014
Love Boat 2015
Golden Ray 2016
Blame Game 2017
Lone Dust 2018
Lucky Wood 2019
Good Earth 2020
Empty Words 2021
Nonage Line 2009
Blue Hell 2010
Cold Blood 2011
Tulip Heat 2012
Red Wrath 2013
Bold Human 2014
Loyal Royal 2015
Lone Cone 2016
Dutch Green 2017
Valley Hill 2018
Kale Hale 2019
Fake Cake 2020
Book Belly 2021