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] Scope of Named Timelines #7047

Closed
fantasai opened this issue Feb 14, 2022 · 10 comments
Closed

[scroll-animations-1] Scope of Named Timelines #7047

fantasai opened this issue Feb 14, 2022 · 10 comments

Comments

@fantasai
Copy link
Collaborator

In the #6674 proposal, timeline names are attached to scroll containers and elements to create scroll timelines and view timelines, and which can then be referenced by animation properties to create animations.

What should be the scope of these names, i.e. which elements can reference a given timeline by name, and what happens if multiple elements declare the same name?

The current spec defines:

A named scroll progress timeline or view progress timeline is referenceable in animation-timeline by:

  • the declaring element itself
  • that element’s descendants
  • that element’s following siblings and their descendants

If multiple elements have declared the same timeline name, the matching timeline is the one declared on the nearest element in tree order, which considers siblings closer than parents. In case of a name conflict on the same element, scroll progress timelines take precedence over view progress timelines.

@andruud
Copy link
Member

andruud commented May 4, 2022

Current proposed text is not unreasonable, but I think this is the first time we would introduce a style resolution dependency between siblings? It's probably not ideal for parallel style resolution (which Firefox does, cc @emilio).

Are important use-cases not satisfied if we downgrade to the following?

  • the declaring element itself
  • that element’s following siblings and their descendants

EDIT: after reading this ~1 month later: I'm not sure what I meant here, and why this would help. Maybe I meant that the scope could be just the element and its descendants (no siblings).


Making these references "global" have been discussed before. (Never mind how for a moment). Is this still on the agenda, or has that idea been discarded?

@brunostasse
Copy link

brunostasse commented May 4, 2022

If I may chime in, I hope the idea of making the timelines reachable globally has not been discarded. I have several use cases which require it.

One of them is in the context of tabs implemented using scroll-snap. Tab names are displayed in a horizontal scroll container above the scroll container for the tabs. As you scroll horizontally through the tabs, the indicator moves to go below the title of the active tab. This is a very common pattern, used in Material design for instance. You might be able to hack around non-global scroll timelines by putting the tab titles container inside the tab container, but that would complicate layout, and is not always possible.

A similar use case is a navigation indicator in a fixed bar next to the title of the section currently in view in a scroll container, with the indicator moving as sections are scrolled through.

The other use cases I have in mind often animate elements which use position fixed and are outside of the scroll container either because they were present in the DOM before the scroll container was added, or for z-ordering reasons.

I fear limiting the access of a scroll timeline to the scroll container and what follows it in the tree would limit its usefulness and push developers to resort to twisted and inconvenient DOM manipulations.

@andruud
Copy link
Member

andruud commented Jun 15, 2022

Global timelines would require a newish concept, e.g. current style is resolved according to the timeline state as of the previous style change event. The spec already has some "as of last frame"-behavior, so it's not necessarily a giant leap. If we are unsure about feasibility, it could make sense to have a vendor prototype/investigate before taking a resolution.

@astearns astearns removed the Agenda+ label Jun 20, 2022
@flackr
Copy link
Contributor

flackr commented Jun 29, 2022

Current proposed text is not unreasonable, but I think this is the first time we would introduce a style resolution dependency between siblings? It's probably not ideal for parallel style resolution (which Firefox does, cc @emilio).

I think counter-increment already creates such a dependency, see the MDN example. Note the use of counter-reset could technically be considered a scope to the counter but works globally for all following elements without it.

Are important use-cases not satisfied if we downgrade to the following?

  • the declaring element itself
  • that element’s following siblings and their descendants

EDIT: after reading this ~1 month later: I'm not sure what I meant here, and why this would help. Maybe I meant that the scope could be just the element and its descendants (no siblings).

Yes, I think @brunostasse touched on one. There are many cases where the animation is outside of the scrolling container.

Making these references "global" have been discussed before. (Never mind how for a moment). Is this still on the agenda, or has that idea been discarded?

The issue with global namespaces is the lack of reusability. E.g. if you had a tabbed component which used a global named scroll timeline to indicate the active tab, adding a second one would result in a name conflict. Whereas if we have proper name scoping they will each work independently.

One way we could make the use case @brunostasse mentioned work while still having an explicit element and its descendents scope would be for the timeline to be able to be named early but choose the actual descendant scroller it references later. This could be thought of as similar to the counter-reset / counter-increment and toggle / toggle-trigger properties. It would require plugging in the eventually found scrolling component to the timeline created earlier.

E.g. maybe this is something like:

<style>
#outer {
  scroll-timeline-name: foo;
}
#scroller {
  scroll-timeline-source: foo; /* Makes this element the source for foo. Last element within the scope to do so wins? */
}
</style>
<div id="outer">
  <div id="scroller"></div>
</div>

Edit: Chatting with @andruud, "global" scope contained by contain: style might be a reasonable way to not have to get this scoped name visibility without introducing more concepts.

@flackr
Copy link
Contributor

flackr commented Jun 29, 2022

The other option without introducing an explicit scope would be to make it visible to all subsequent elements in DOM order (i.e. ancestor siblings as well). To make the case @brunostasse brought up work you'd have to put the tabs after the scroller so that the scroller could define the scroll timeline before the tabs. Then you could use grid or flex layout to put the tabs above even though they are declared later. This doesn't seem as nice from a developer standpoint though.

@andruud
Copy link
Member

andruud commented Jun 30, 2022

@flackr Something like what you propose seems good? I assume that in that model, the timeline established by foo would be visible for descendants only? (I.e. not siblings of #outer, and if that's what the author wanted, they would instead declare foo on the parent of #outer). It still requires "as of last frame"-behavior, but like I said we can try to work with that.

@css-meeting-bot
Copy link
Member

css-meeting-bot commented Aug 3, 2022

The CSS Working Group just discussed scope of named timelines, and agreed to the following:

  • RESOLVED: scope of named timelines is across flattened tree
  • RESOLVED: timeline search looks at preceding siblings and ancestors, recursively
The full IRC log of that discussion <TabAtkins> Topic: scope of named timelines
<TabAtkins> github: https://github.com//issues/7047
<TabAtkins> fantasai: when you name a timeline with the css properties, what's the name scope; which elements have access?
<TabAtkins> fantasai: easiest is to work the same as counter
<TabAtkins> fantasai: so which scope should we have for these things - descendants and siblings (like counters) or expand further? and if further, what precedence rules?
<TabAtkins> TabAtkins: further question - does this extend into shadows
<TabAtkins> fantasai: good question, probably no
<TabAtkins> TabAtkins: suspect i agree
<TabAtkins> flackr: sounds easy to resolve
<fantasai> TabAtkins: Use either the pre-flattened or post-flattened tree
<fantasai> emilio: The flattened tree doesn't know about shadows
<fantasai> emilio: counters use the flattened tree
<fantasai> TabAtkins: It is true that other than selectors, CSS is defined over the flattened tree
<fantasai> TabAtkins: what's easier?
<fantasai> astearns: Is there a reason for them to descend or not?
<fantasai> TabAtkins: would let you to refer to timelines defined outside the shadow
<fantasai> miriam: Seems like it could be useful
<fantasai> i/TabAtkins: further/scribe+ fantasai
<fantasai> TabAtkins: do counters use the flat tree?
<fantasai> emilio: in Gecko they use flattened tree for sure
<fantasai> dbaron: I thought both HTML list numbering and counters don't use the flattened tree
<fantasai> dbaron: pretty sure about HTML list numbering
<fantasai> emilio: when Mats worked on list counters, the CSSWG resolved to use the flattened tree
<fantasai> emilio: and we fixed it very recently
<fantasai> dbaron: So 2 weeks ago I was helping a new engineer working on Blink, and advised it was to not use flattened tree
<fantasai> TabAtkins: outside of the display spec and possibly scoping, which define how flattened tree works
<fantasai> TabAtkins: the rest of CSS doesn't mention a tree specifically, just uses parent-child relationships
<emilio> https://github.com//issues/2679 is the relevant issue IIRC
<fantasai> TabAtkins: so don't see how you'd be reading that
<fantasai> dbaron: HTML defines it twice, once not using the flattened tree
<fantasai> dbaron: and a second time in the rendering section in terms of CSS counters
<fantasai> TabAtkins: HTML might do something weird, the way their list counters work is bizarre and based on back-compat
<emilio> https://bugzilla.mozilla.org/show_bug.cgi?id=1477524 is where we fixed it
<fantasai> astearns: Sounds like figuring out how other parts of CSS work as precedent is not a great idea
<fantasai> TabAtkins: well maybe not HTML list counters
<fantasai> astearns: Only opinion I've heard so far is that so far it would be better to pierce the shadow DOM
<fantasai> astearns: we can spec it either way
<fantasai> miriam: My sense would be that authors will want their web components to respond to these timelines, so having it available in the shadow DOM seems useful
<fantasai> astearns: Anyone wants to argue against having timelines pierce shadow boundaries?
<fantasai> flackr: Is it normal to coordinate thing outside shadow DOM and stuff inside it?
<fantasai> TabAtkins: depends on your usage
<fantasai> TabAtkins: if you're ... or ???; entirely opposite use cases
<fantasai> miriam: Shadow can usually see things outside, it's just usually the other way around that we don't do
<fantasai> dbaron: It does seem a little awkward to conclude without anyone from WebKit
<fantasai> dbaron: they tend to have a unified opinion on shadow DOM
<fantasai> astearns: They're not here, so we can conclude without them and they can object if they want
<fantasai> astearns: we should try to make progress, can always circle back
<fantasai> astearns: proposed resolution is that the scope of named timelines uses the flattened tree
<fantasai> TabAtkins: I do think that's the right way to do it now
<fantasai> astearns: objections?
<fantasai> RESOLVED: scope of named timelines is across flattened tree
<bramus> q+
<flackr> q+
<TabAtkins> fantasai: back to original issue, don't have much of an opinion
<TabAtkins> fantasai: current spec says scope is the element itself and its descendants
<TabAtkins> fantasai: and its siblings and their descendants
<TabAtkins> TabAtkins: identical to counters
<TabAtkins> fantasai: so question is if we want this to expand to document-global?
<astearns> ack bramus
<TabAtkins> bramus: i think there are some use-cases for it
<TabAtkins> bramus: like if an element is scrolling and elements elsewhere in the tree are animating along it
<TabAtkins> bramus: so like in your body a fixpos element, wanting to look at the scrolling element to reveal itself at one point
<astearns> ack flackr
<TabAtkins> flackr: there are definitely use-cases for global
<TabAtkins> flackr: only concern is these are often used in components, like progress thru a carousel
<TabAtkins> flackr: so want to support multiple things with a single name and have animations bind to the right one
<TabAtkins> flackr: so i think we need some well-defined way to override names
<TabAtkins> flackr: so in the issue i was proposing tree order
<TabAtkins> flackr: so the most recenty-defined thing is chosen
<TabAtkins> fantasai: so walk backwards thru the tree
<TabAtkins> dbaron: so you're saying you'd include something established by the descendant of a previous sibling?
<TabAtkins> flackr: yeah,a nd there are use-cases for this
<TabAtkins> emilio: that can be a perf hit when doing the lookup
<TabAtkins> flackr: in perf, for each name save a list of tree-order indexes
<TabAtkins> emilio: we don't have such a list
<TabAtkins> flackr: oh i thought browsers had that
<TabAtkins> emilio: wouldn't that break with mutations? lots of updates when you insert
<TabAtkins> flackr: there are ways to get around that
<fantasai> TabAtkins: there is a DOM API for comparing positions, but it's slow
<astearns> ack dbaron
<TabAtkins> dbaron: i would caution against using counters as a precedent
<bkardell_> q+
<TabAtkins> dbaron: they were designed with a weird set of requirements that led to their design, and i wouldn't assume that's waht you want to copy unless it's the same requirements
<TabAtkins> flackr: use-case is there are things outside the scroller that want to update based on the scroller's position
<TabAtkins> flackr: like specific, progress thru a carousel, have dots update to show what pane you're on
<astearns> ack fantasai
<TabAtkins> fantasai: if we want to make it global thru a page, flattened tree isn't ideal
<TabAtkins> fantasai: would want to check siblings first instead of cousins
<TabAtkins> fantasai: So i think we want an order where you look up siblings, then parent, etc
<TabAtkins> TabAtkins: So never descend the tree, just go up and sideways?
<TabAtkins> fantasai: Maybe. if we want more full global could deprioritize going down first
<TabAtkins> flackr: I think that would do good enough
<astearns> ack bkardell_
<TabAtkins> astearns: unsure how we'd prioritize
<TabAtkins> flackr: Could punt going down for now
<fantasai> TabAtkins: so searching up and sideways... not quite counter scope
<fantasai> TabAtkins: so any of your siblings, your ancestors, and their siblings, but not going back down
<bramus> q+
<TabAtkins> astearns: efficiency concerns?
<TabAtkins> TabAtkins: that's an ancestor walk with some sibling walks along the way
<TabAtkins> emilio: eh, with big trees can still be annoying
<TabAtkins> fantasai: assume there's some caching around
<TabAtkins> emilio: yeah
<TabAtkins> emilio: depending on impl, might be tricky to invalidate properly
<TabAtkins> emilio: if you're building the tree you might not have later sibling info yet
<ydaniv> q+
<TabAtkins> emilio: the level of trickiness is varying
<TabAtkins> flackr: in my opinion the use-cases that need siblings can use previous siblings and then use grid/flex to swap around layout
<astearns> ack bramus
<TabAtkins> fantasai: don't want to force bad source ordering tho
<TabAtkins> bramus: wondering if this still allows for a carousel to reach the end of the carousel and animate something else that's not a sibling or an ancestor?
<TabAtkins> fantasai: yes, it searches from the element trying to *find* the timeline
<bkardell_> q+
<TabAtkins> bramus: so this wouldn't work then?
<TabAtkins> fantasai: depends on what you're trying to do
<TabAtkins> flackr: think for a carousel it means you can't have ac ontainer on the scrolling element
<fantasai> TabAtkins: Have we considered swapping between local and global scope?
<fantasai> TabAtkins: so that local things can do the cheap thing
<fantasai> TabAtkins: but can expand the scope when needed
<TabAtkins> bramus: and last one wins if there's a collision
<TabAtkins> emilio: do we have a good story of what happens we you mutate the tree?
<TabAtkins> emilio: Is it well-defined when you perform this lookup, and if you need to change stuff when this changes
<fantasai> TabAtkins: I think you're asking about batch and flush layout?
<fantasai> emilio: not necessarily
<flackr> qq+
<fantasai> emilio: You choose the same timeline name as you have earlier, do you restart the animation? What do you do? When and how long does your choice of timeline last?
<astearns> ack flackr
<Zakim> flackr, you wanted to react to bramus
<fantasai> flackr: our name lookup is important for this because, if it's global it can happen based on style of any element, but if just preceding siblings and ancestors, we can know which scroll timeline you're looking at
<fantasai> flackr: we don't restart timelines, it's defined in Web Animations what to do
<bkardell_> q-
<bramus> q+
<fantasai> flackr: but expectation is that as part of doing style and layout, we can update to the new correct timeline and resolve the timeline according to position in that timeline
<astearns> ack ydaniv
<TabAtkins> ydaniv: like robert said, if we start with something simple and get more complex lookups later, maybe using JS api people can make more complex usages
<TabAtkins> astearns: not a bad idea. have to consider how we expand this in css, tho - adding keywords to determine the scope rules?
<TabAtkins> fantasai: tab suggested a timeline-scope
<astearns> ack bramus
<TabAtkins> bramus: what if you made the switch automatic? if you have non-named scroll timelines they're local, but named timelines are global?
<TabAtkins> flackr: how would you refer to an anonymous timeline for a later timeline?
<TabAtkins> bramus: from the scroll() function..?
<TabAtkins> fantasai: that doesn't work in view-timeline
<TabAtkins> fantasai: I don't think we shoudl switch on name, we want something more ergonomic
<flackr> q+
<astearns> ack flackr
<TabAtkins> fantasai: Could also have the model where an element can declare it's the scope, but the driver for the timeline is later in the tree and binds itself
<TabAtkins> flackr: I suggest we use the siblings/ancestors and add a switch later
<TabAtkins> flackr: and for impl, when we discover a scroll timeline has changed we schedule another style pass, and we should be able to handle mutations in that pass
<TabAtkins> flackr: if a named timeline has changed, we need to restart style and layout with that timeline
<TabAtkins> astearns: and this is just earlier siblings, right?
<TabAtkins> flackr: Yes, tho i'm open to later siblings
<TabAtkins> TabAtkins: Later sibs can force us to rely on waiting for th enetire document to load
<TabAtkins> emilio: You have to do that anyway for :nth-clast-child(), etc
<TabAtkins> emilio: We have some examples, but could be tricky
<TabAtkins> flackr: i think once we have experience we could ahve stronger rationale one way or the other
<TabAtkins> astearns: fair. until we have experience i suggest going with earlier siblings only, seems more likely to succeed
<TabAtkins> fantasai: one way to avoid having to look at all document is to look at closest sibling, in either direction
<TabAtkins> TabAtkins: I'm uncomfortable with a completely novel lookup mechanism without strong justification
<TabAtkins> fantasai: justification is the elements are usually near each other
<TabAtkins> astearns: That's only different from a simple walk if there are name collisions, tho
<TabAtkins> flackr: and there usually shouldn't be
<TabAtkins> astearns: so I recommend the scope of timelines is that elements will look for timelines among their previous siblings and ancestors, transitively. can add a switch for more complex behavior in the future.
<TabAtkins> astearns: Amendments?
<TabAtkins> flackr: I like it. I think elika's earlier concern about not forcing authors to reorder their content - usually scroll timelines are decorative, so content order is less important.
<fantasai> TabAtkins: forcing a specific order isn't a problem if it doesn't interfere with semantic ordering
<TabAtkins> TabAtkins: We don't like forcing it if unnecessary, but okay if needed
<TabAtkins> fantasai: should look into the idea of declaring a named scope and letting a scroller bind to it, think that will solve a lot of issues
<TabAtkins> fantasai: I'll explore it in this issue, split out if needed.
<TabAtkins> proposed resolution: timeline search looks at ancestors and preceding siblings, recursively
<TabAtkins> (and we'll revisit with impl experience as needed)
<TabAtkins> RESOLVED: timeline search looks at preceding siblings and ancestors, recursively

See official minutes

@fantasai
Copy link
Collaborator Author

fantasai commented Sep 7, 2022

Okay, the current state of the spec reflects the resolutions. This is probably fine for now, but we're not addressing all use cases and it might be worth looking into the ability to split the declaration of a timeline (together with its scoping) from its actual attachment to a scroll container.

This would allow authors, for example, to declare a name on a subtree and make it available to all descendants of that subtree, and attach it to a scroll container that is a descendant within the subtree. (At the top level, declaring the name on the root element would make it global.)

Maybe something like scroll-timeline-attachment: local | defer | ancestor | closest where:

  • local has the current behavior of binding the name to this element’s scroll container
  • defer declares and scopes the name, but does not bind it to a scroll container
  • ancestor looks up the ancestor chain for a matching timeline name and attaches to that instance; failing a match, declares it locally
  • closest looks back up the tree including previous siblings for a matching timeline name (same lookup as animation-timeline), and attaches to the first matching instance; failing a match, declares it locally

@fantasai
Copy link
Collaborator Author

fantasai commented Dec 7, 2022

@bramus
Copy link
Contributor

bramus commented Jan 23, 2023

@fantasai I think this one can be closed. Discussion of #7047 (comment) can continue in #7759, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Wednesday
Development

No branches or pull requests

7 participants