Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[scroll-animations-1] CSS @scroll-timeline: Allow <element-offset> selector() to point to self #5884

Closed
bramus opened this issue Jan 21, 2021 · 6 comments
Labels

Comments

@bramus
Copy link
Contributor

bramus commented Jan 21, 2021

When animating an element based on the location of the element itself inside a scroll container using scroll-animations, you need to set a start and end. To configure this one needs an Element-based Offset.

When applying such an animation using the JavaScript it's pretty easy to set this up for many elements, as you can use a loop and refer to the current element by referring to its local var. See startScrollOffset/endScrollOffset in the code snippet below.

const $listView = document.querySelector('ul');
const $listItems = document.querySelectorAll('ul > li');

$listItems.forEach(($listItem) => {
	// Slide-In
	new Animation(
		new KeyframeEffect(
			$listItem,
			{
				transform: ["translateX(-100%)", "translateX(0)"],
			},
			{ duration: 1, fill: "both" }
		),
		new ScrollTimeline({
			scrollSource: $listView,
			timeRange: 1,
 			fill: "both",
			startScrollOffset: { target: $listItem, edge: 'end', threshold: 0 },
			endScrollOffset: { target: $listItem, edge: 'end', threshold: 1 },
		})
	).play();
	
});

While trying to recreate this using the CSS @​scroll-timeline I've noticed that this this type of concise code is exactly possible as the <element-offset> type requires a selector(<id-selector>). Because of that one has to create @​scroll-timeline's for every individual li

@supports (animation-timeline: works) {
	@keyframes fade-in {
		from {
			transform: translateX(-100%);
		}
		to {
			transform: translateX(0);
		}
	}

	li {
		transform: translateX(-100%);
		animation: 1s fade-in linear forwards;
	}

        /* Scroll Timeline for first item */
	@scroll-timeline list-item-1 {
		source: selector(ul);
		start: selector(#list-item-1) end 0;
		end: selector(#list-item-1) end 1;
		time-range: 1s;
	}
	#list-item-1 {
		animation-timeline: list-item-1;
	}

        /* Scroll Timeline for second item */	
	@scroll-timeline list-item-2 {
		source: selector(ul);
		start: selector(#list-item-2) end 0;
		end: selector(#list-item-2) end 1;
		time-range: 1s;
	}
	#list-item-2 {
		animation-timeline: list-item-2;
	}
	
        /* Scroll Timeline for third item */
	@scroll-timeline list-item-3 {
		source: selector(ul);
		start: selector(#list-item-3) end 0;
		end: selector(#list-item-3) end 1;
		time-range: 1s;
	}
	#list-item-3 {
		animation-timeline: list-item-3;
	}

       /* Now rinse and repeat for the next 100 elements … */

}

To not have to repeat that @​scroll-timeline definition over and over again for each and every single li, it'd be handy if the selector() part in the <element-offset> could be configured in such a way that it refers to the element being animated itself, e.g. this or self.

That way our code would become something like this:

@supports (animation-timeline: works) {
	@keyframes slide-in {
		from {
			transform: translateX(-100%);
		}
		to {
			transform: translateX(0);
		}
	}

	@scroll-timeline slide-in-in-parent-ul {
		source: selector(ul);
		start: selector(self) end 0;
		end: selector(self) end 1;
		time-range: 1s;
	}

	li {
		transform: translateX(-100%);
		animation: 1s slide-in linear forwards slide-in-in-parent-ul;
	}

}

I see this as a huge benefit to the spec, as this would open up the way for animation/code reuse.

Would this be something worth pursuing, or am I overlooking something in the spec that would already allow this?

@bramus
Copy link
Contributor Author

bramus commented Jan 21, 2021

Afternoon idea: a value of auto for <element-offset> seems like something more CSS-like instead of the previously mentioned selector(self)

@johannesodland
Copy link

This is crucial for the proposal to succeed. Building one @scroll-timeline per animated element is not feasible.
But it is not enough to only reference the animated element (self).

Problem: Multiple choreographed animations
The "Scrollable picture-story" use case in the draft showcases the need for choreographing multiple elements together sharing one timeline: https://drafts.csswg.org/scroll-animations-1/#scrollable-animation-usecase

In the example this is solved by having a @scroll-timeline with a container based offset. This only works if there is no more than one "scrollable picture-story", or if it is possible to build a timeline for each story individually.

In the real world one page would contain many similar "scrollable picture stories", often user/author generated. We don't build unique selectors for component instances, and we should not have to build unique timelines for component instances.

(For an example of a page with multiple animated picture-stories check the yellow dotted lines here: https://www.nrk.no/norge/xl/pa-innsiden-av-hagens-hus-1.15206175)

Referencing the animated element (self)
The timeline must be able to reference the animated element.

This solves part of the problem. But for multiple element animations to be coordinated, it is necessary to reference a common parent of the animated elements.

In the "scrollable picture story" the circles might not be placed in the same location, and if the timeline reference each individual animated element, they will end up with different timelines. The choreography will break.

Referencing a common parent
We need to reference a common parent of the animated elements. Let's say both the circles are children of a figure, as illustrated by the black outline in the example, then we will reference the figure in the common timeline.

We could do this using the relational pseudo-class:

figure:has(> self)

A simplified version of the extended use case could then be solved as follows:

@media (prefers-reduced-motion: no-preference) {
  .circle {
    animation-timeline: collision-timeline;
  }
  
  .left-circle {
    animation-name: left-circle;
  }

  .right-circle {
    animation-name: right-circle;
  }

   /* Timeline must be able to reference a common parent of the animated elements to choreograph multiple elements */
  @scroll-timeline collision-timeline {
    source: selector(#container);
    scroll-offsets: selector(figure:has(> self)) end, selector(figure:has(> self)) start;
  }

  @keyframes left-circle {
    to { transform: translateX(300px); }
  }

  @keyframes right-circle {
    to { transform: translateX(-300px); }
  }
}

@johannesodland
Copy link

I'm not sure if self would be the best way to reference the animated element. It could be confusing in css-nesting.

I don't have a better suggestion though.

@bramus
Copy link
Contributor Author

bramus commented Feb 24, 2021

As I came to understand on Twitter per tweet by @majido (ref):

We envisioned this idea when working on element based offset. We considered v2 just to get the basic out the door first. Glad you are giving this feedback.

In the linked-to issue #4337 this is covered in more detail, and the (initially) proposed keyword is :animaton-target

Initially, it may be enough for <element()> to only support #ID selector and also a special syntax element(:animaton-target) that selects the animation target itself. Later this can be expanded to support more complex selectors.

@fantasai fantasai added the scroll-animations-1 Current Work label Mar 23, 2021
@bramus
Copy link
Contributor Author

bramus commented Jun 23, 2021

Idea: what if selector() acted more like document.querySelector, with support for & as borrowed from css-nesting?

That would:

  • be in line with other specs/syntaxes, not introducing any new keywords such as self
  • extend its current capabilities from only IDs to anything you want
  • be familiar to CSS developers as they already know the possibilities

@bramus
Copy link
Contributor Author

bramus commented Jun 16, 2022

Closing this issues as the spec is being rewritten to not use @scroll-timeline – See #6674 for details.

@bramus bramus closed this as completed Jun 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants