CSS Conf Colombia 2021
Bramus Van Damme
Chromium 89 with the #experimental-web-platform-features flag enabled required
Hardware accelerated Scroll-Linked Animations, running off main thread without any need for any JavaScript!
Scroll-Linked Animations
Scroll-Triggered 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;
}
💁♂️ 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.
🔥 TIP: Always set time-range to the exact same time as the animation-duration, unless you have a very good reason not to.
☝️ In an older version of the spec the scroll-offsets had to be defined as start and end.
Animations based on the location of an element within the scroll-container
@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;
}
selector(#id)
end|start
[0..1]
@scroll-timeline box-enters-and-leaves-scrollport {
scroll-offsets:
selector(#target) end 0,
selector(#target) start 0
;
time-range: 1s;
}
end 0
→ start 0
end 1
→ start 1
end 0
→ end 1
start 1
→ start 0
@scroll-timeline revealing-image-timeline-1 {
scroll-offsets:
selector(#revealing-image-1) end 0.5,
selector(#revealing-image-1) end 1
;
time-range: 1s;
}
@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;
}
@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
;
}
@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;
}
☝️ What’s remarkable here is that the @scroll-timeline is tracking #sectionPin, but the animation is applied to it’s child element .pin-wrap. This is truly one of the powers of @scroll-timeline.
🐛 The items at the start and stop of the list are rendering wrongly due to Chromium Bug 1174838
<ul>
element is the scroll container, and the <li>
elements inside scroll.<li>
elements themselves switch z-index, and their contained <img />
elements are transformed.position: sticky
and vary the padding-top
to achieve the offset.☝️ Thanks to playing with animation-delay
I’m able to use one shared @scroll-timeline
for each card