Description
The problem
With Scroll-Driven Animations, authors often want to run animations offsetted against one of the segment edges. For example, to run an animation up to "100px from the start of the range". For this, authors can do calculations in keyframe offsets, e.g. animation-range: entry 0% entry calc(0% + 100px);
.
While this syntax works perfectly fine, it is not allowed everywhere. Depending on which keyframes format authors use, they can or can’t do calculations.
- WAAPI with keyframes as an object with rangeStart + rangeEnd set in the options: ✅ Allowed
- WAAPI with keyframes as an array of objects that include range information: ❌ Not allowed
- CSS Animations with animation-range-start + animation-range-end: ✅ Allowed
- CSS Animations with
@keyframes
that include range information: ❌ Not allowed
This leads to frustration, as recently expressed by Matthew Perry on Twitter:
It's stuff like this that leads people back to using animation libraries. And these will only be more bloated if they have to jump through hoops like converting one animation into multiple animations to support multistop
I think we should close this gap, and allow calculated keyframes across the board.
Performing calculations in the offsets
Web Animations API
If they want to – and they often do – authors can also do calculations in the offset
value. When passing in keyframes as an object, they can adjust the values for rangeStart
and/or rangeEnd
:
document.querySelectorAll('#list-view li').forEach($li => {
const timeline = new ViewTimeline({
subject: $li,
axis: 'block',
});
$li.animate({
opacity: [ 0, 1 ],
transform: [ 'translateY(100%)', 'translateY(0)'],
}, {
fill: 'forwards',
timeline,
rangeStart: 'entry 0%',
rangeEnd: 'entry calc(0% + 100px)', // 👈 This line
});
$li.animate({
opacity: [ 1, 0 ],
transform: [ 'translateY(0)', 'translateY(-100%)'],
}, {
fill: 'forwards',
timeline,
rangeStart: 'exit calc(100% - 100px)', // 👈 This line
rangeEnd: 'exit 100%',
});
});
However, when passing in an array of objects as the keyframes, it is not accepted.
document.querySelectorAll('#list-view li').forEach($li => {
const timeline = new ViewTimeline({
subject: $li,
axis: 'block',
});
$li.animate([
{ opacity: 0, offset: 'entry 0%' },
{ opacity: 1, offset: 'entry calc(0% + 100px)' }, // ❌ Does not work
{ opacity: 1, offset: 'exit calc(100% - 100px)' }, // ❌ Does not work
{ opacity: 0, offset: 'exit 100%' },
], {
fill: 'both',
timeline,
});
});
In both snippets above, authors get back this error:
Uncaught TypeError: Failed to execute 'animate' on 'Element':
timeline offset must be of the form [timeline-range-name]
CSS Animations
Looking at the CSS variant of SDA, it’s similar there: when using animation-range
an author can do calculations, but when creating a set of keyframes with @keyframes
they can not.
@keyframes f {
from { opacity: 0; }
to { opacity: 1; }
}
el {
animation-name: f;
animation-timeline: view();
animation-range: entry 0% entry calc(0% + 100px); /* ✅ Allowed */
}
@keyframes f {
entry 0% { opacity: 0; } /* ✅ Allowed */
entry calc(0% + 100px) { opacity: 1; } /* ❌ Not allowed */
}
el {
animation-name: f;
animation-timeline: view();
}
The Cause
In both cases (CSS or WAAPI), this is because of the syntax of keyframe selectors. As per spec, the offsets are limited to <percentage>
s when combined with a <timeline-range-name>
:
<keyframe-selector> = from | to | <percentage [0,100]> | <timeline-range-name> <percentage>
This in contrast to animation-range-start
and animation-range-end
which do accept lengths (spec):
animation-range-start = [ normal | <length-percentage> | <timeline-range-name> <length-percentage>? ]#
Proposed Solution
Allow <length-percentage>
– instead of only <percentage>
– in <keyframe-selector>
when combined with <timeline-range-name>
.
By allowing <length-percentage>
, authors can perform these calculations across the board, independent of which format they use to create their animation. The proposed new syntax is this:
<keyframe-selector> = from | to | <percentage [0,100]> | <timeline-range-name> <length-percentage>
As a result, the failing code snippets listed above will start to work fine.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status