Scroll-Linked Animations with CSS @scroll-timeline

CSS Café 2021

@bramus

image/svg+xml
Avatar of Bramus

Bramus Van Damme

@bramus

🚨

Experimental CSS Features Ahead!

Chromium 89 with the #experimental-web-platform-features flag enabled required

🤩

Exciting
Features Ahead!

Hardware accelerated Scroll-Linked Animations, running off main thread without any need for any JavaScript!

Scroll-Linked Animations with CSS @scroll-timeline

Scroll-Linked Animations

Scroll-Triggered Animations

Scroll-Linked Animations

Animations based on the scroll-progress of a scroll-container

@keyframes adjust-progressbar {
    from {
        transform: scaleX(0);
    }
    to {
        transform: scaleX(1);
    }
}

@scroll-timeline progressbar-timeline {
    time-range: 1s;
}

#progressbar {
    animation: 1s linear forwards adjust-progressbar;
    animation-timeline: progressbar-timeline;
}
  1. A CSS Animation
  2. A Scroll-Timeline
  3. A link between both

Scroll-Linked Animation Key Components

💁‍♂️ A Scroll-Timeline is a type of timeline whose actual time value is determined not by a ticking clock but by the progress of scrolling in a scroll container.

  1. time-range
  2. scroll-offsets
  3. orientation
  4. source

Scroll-Timeline Descriptors

  1. time-range
  2. scroll-offsets
  3. orientation
  4. source

Scroll-Timeline Descriptors

🔥 TIP: Always set time-range to the exact same time as the animation-duration, unless you have a (very good) reason not to.

  1. time-range
  2. scroll-offsets
  3. orientation
  4. source

Scroll-Timeline Descriptors

☝️ In an older version of the spec the scroll-offsets had to be defined as start and end.

  1. time-range
  2. scroll-offsets
  3. orientation
  4. source

Scroll-Timeline Descriptors

☝️ As the selector() function requires an explicit #id, you can't reuse your timelines. Instead you'll have to duplicate your code.

  1. Cover to Header
  2. Parallax Header
  3. Full-Screen Panels with Navigation
  4. Full-Screen Panels with Navigation (alt)

More Demos

Other Tips / Findings

🔥 TIP: Always set animation-timing-function to linear
unless you have a reason not to.

☝️ It's not possible to use CSS Custom Properties as values for descriptors

Fin?

Scroll-Linked Animation with Element-Based Offsets

Animations based on the location of an element within the scroll-container

Element-Based Offsets in Pseudo-Code

@scroll-timeline box-enters-and-leaves-scrollport {
  scroll-offsets:
    “the box is at the bottom edge of the scrollport”,
    “the box is above the top edge of the scrollport”
  ;
  time-range: 1s;
}

Element-Based Offsets in CSS:
<element-offset>

  1. target: selector(#id)
  2. edge: end|start
  3. threshold: [0..1]
@scroll-timeline box-enters-and-leaves-scrollport {
  scroll-offsets:
    selector(#target) end 0,
    selector(#target) start 0
  ;
  time-range: 1s;
}

Typical
<element-offset>
Combinations

  • end 0start 0
    = intersecting scrollport
  • end 1start 1
    = in between scrollport edges
  • end 0end 1
    = enter from bottom into scrollport
  • start 1start 0
    = exit at top from scrollport

Revealing Image

@scroll-timeline revealing-image-timeline-1 {
  scroll-offsets:
    selector(#revealing-image-1) end 0.5,
    selector(#revealing-image-1) end 1
  ;
  time-range: 1s;
}

Slide-In Contact List

@keyframes slide-in {
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

li {
  animation: 1s slide-in ease-in forwards;
}

@scroll-timeline list-item-15 {
  source: selector(#list-view);
  scroll-offsets:
    selector(#list-item-15) end 0,
    selector(#list-item-15) end 1
  ;
  time-range: 1s;
}
#list-item-15 {
  animation-timeline: list-item-15;
}

Slide-In-Out Contact List

@scroll-timeline tl-list-item-12-appear {
  source: selector(#list-view);
  scroll-offsets:
    selector(#list-item-12) end 0,
    selector(#list-item-12) end 1
  ;
  time-range: 1s;
}
@scroll-timeline tl-list-item-12-disappear {
  source: selector(#list-view);
  scroll-offsets:
    selector(#list-item-12) start 1,
    selector(#list-item-12) start 0
  ;
  time-range: 1s;
}

#list-item-12 > * {
  animation:
    1s li-appear linear tl-list-item-12-appear,
    1s li-disappear linear tl-list-item-12-disappear
  ;
}

Horizontal Scroll Section

@scroll-timeline horizontal-section {
  source: selector(body);
  scroll-offsets:
    selector(#sectionPin) start 1,
    selector(#sectionPin) end 1
  :
  time-range: 1s;
}

@keyframes move-horizontal-section {
  to {
    transform: translateX(calc(-100% + 100vw));
  }
}
  
.pin-wrap {
  position: sticky;
  top: 0;

  animation: 1s linear move-horizontal-section forwards;
  animation-timeline: horizontal-section;
}

🐛 You'll see the animation glitch upon reversal (when scrolling back up), due to Chromium Bug 1175289

Cover Flow

🐛 The items at the start and stop of the list are rendering wrongly due to Chromium Bug 1174838

  1. The <ul> element is the scroll container, and the <li> elements inside scroll.
  2. The <li> elements themselves switch z-index, and their contained <img /> elements are transformed.

Stacking Cards

  1. Keep items in view with position: sticky and vary the padding-top to achieve the offset.
  2. Lay out all cards in their wrapper using CSS Grid with fixated rows. Before they stick their padding doesn't appply.

☝️ Thanks to playing with animation-delay I’m able to use one shared @scroll-timeline for each card

Fin?

Browser Bugs

Fin.