Skip to content

[scroll-animations-1] Allow <length-percentage> in <keyframe-selector> when combined with <timeline-range-name> #10000

Open
@bramus

Description

@bramus

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

No one assigned

    Type

    No type

    Projects

    Status

    Friday afternoon

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions