Reverse-Scrolling Columns with CSS Scroll-Timeline

The other day I saw “Alternate Column Scroll” by Manoela Ilic float by. It’s a tutorial/demo in which some of the content columns scroll in the opposite direction, powered by Locomotive Scroll.

Based on Manoela’s demo I recreated the scrolling part with CSS @scroll-timeline.

~

~

# The Basic Structure

I based myself upon Manoela’s original markup and start with 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>
</div>

The basic layout is done using CSS Grid:

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

Inside each .column, the contained .column__item elements are positioned using CSS Flexbox:

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

💡 We choose flexbox here because we’ll change flex-direction for the .column-reverse columns later on.

Combined, the starter template looks like this:

See the Pen Alternate Column Scroll (CSS @scroll-timeline first + JS ScrollTimeline fallback + Polyfill) by Bramus (@bramus) on CodePen.

~

# Implementing Reverse Scrolling using CSS @scroll-timeline

# Adjusting the columns

In browsers that support CSS @scroll-timeline we shift the .column-reverse columns up by translating them by -100%. As they are now entirely off-screen, we counteract that by adding 100vh. That way their bottom edge touches the bottom edge of the viewport.

/* Shift entire column up, but not so much that it goes out of view */
.column-reverse {
  transform: translateY(calc(-100% + 100vh));
}

As we don’t want .columns to grow by that translation, we also prevent the content from bleeding out of it.

/* As we're about to shift content out of .columns, we need it to hide its overflow */
.columns {
  overflow-y: hidden;
}

Finally, as the columns will be scrolling in a reversed direction, we also reverse the order for the items inside each column. That way the first item will be at the bottom of the reversed column.

/* Flip item order in reversed columns */
.column-reverse {
  flex-direction: column-reverse;
}

💡 To detect whether a browser supports CSS @scroll-timeline we use @supports and feature check on the telltale animation-timeline CSS Property.

/* Scroll-Timeline Supported, Yay! */
@supports (animation-timeline: works) {
  …
}

Browsers that support @scroll-timeline also support animation-timeline.

~

# Setting up + Attaching the Animation and Scroll-Timeline

Our ScrollTimeline is the default Scroll-Timeline of scrolling through the document from top to bottom. To set it up we don’t need anything special.

/* Set up scroll-timeline */
@scroll-timeline scroll-in-document {
    source: auto; /* Default scroll-timeline: scrolling in the document */
}

The Animation itself starts at the translation we’ve already done (-100% + 100vh). The end position is a translation in the opposite direction: 100% down, but minus 100vh to keep it on-screen.

/* Set up Animation */
@keyframes adjust-position {
  /* Start position: shift entire column up, but not so that it goes out of view */
  from {
    transform: translateY(calc(-100% + 100vh));
  }
  /* End position: shift entire column down, but not so that it goes out of view */
  to {
    transform: translateY(calc(100% - 100vh));
  }
}

☝️ I know, you can omit the from here, but I’m including it here for educational purposes.

Using the animation-timeline we finally link up our ScrollTimeline to the .column-reverse elements:

/* Hook our animation with the timeline to our columns */
.column-reverse {
  animation: 1s adjust-position linear forwards;
  animation-timeline: scroll-in-document;
}

💭 Curious to learn more about CSS @scroll-timeline? Go read this article here on bram.us covering @scroll-timeline in more detail.

~

# All together now

Combined, our demo now looks like this:

See the Pen Reverse-Scrolling Columns with CSS Scroll-Timelinel (+ JS ScrollTimeline Polyfill Fallback) by Bramus (@bramus) on CodePen.

👨‍🔬 The demo above will only work in Chromium 89+ with the #experimental-web-platform-features flag enabled through chrome://flags. That’s because it’s the only browser that supports CSS @scroll-timeline at the time of writing.

~

# Implementing Reverse Scrolling with JS, for browsers that don’t speak CSS @scroll-timeline

# The Polyfill

In browsers that don’t support CSS @scroll-timeline, the Scroll-Timeline Polyfill by Robert Flack is loaded and used.

// Polyfill for browsers with no Scroll-Timeline support
import "https://flackr.github.io/scroll-timeline/dist/scroll-timeline.js";

The polyfill will register itself when needed.

🤔 Polyfill?

A polyfill is a piece of code (or plugin) that provides the technology that you, the developer, expect the browser to provide natively — What is a Polyfill?

~

# WAAPI + ScrollTimeline

The animation itself is a regular WAAPI animation and does exactly the same as the CSS Animation described above. The animation is, however, extended with a linked ScrollTimeline instance the polyfill provides us.

Above that, the entire code is wrapped inside a little snippet that feature detects CSS support for animation-timeline. When it’s not supported, the contained code will be executed.

// Fallback for browsers that don't support CSS ScrollTimeline
if (!CSS.supports("animation-timeline: foo")) {

  // 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 ScrollTimeline instance
  const timeline = new ScrollTimeline({
    scrollSource: document.documentElement,
    timeRange: 1,
    fill: "both"
  });

  // 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))"
        ]
      },
      {
        duration: 1,
        fill: "both",
        timeline
      }
    );
  });
}

💭 Curious to learn more about WAAPI + ScrollTimeline? Go read this article on CSS-Tricks that covers WAAPI + ScrollTimeline in more detail.

~

# All together now

With that, our final demo now becomes this:

See the Pen Reverse-Scrolling Columns with CSS Scroll-Timelinel (+ JS ScrollTimeline Polyfill Fallback) by Bramus (@bramus) on CodePen.

I know, there’s a FOUC here. I’ll leave it to you, reader, as an exercise to solve.

~

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Leave a comment

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.