w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.46k stars 658 forks source link

[scroll-animations-1] Add a new named timeline range that is independent of subject dimensions. #9617

Open johannesodland opened 10 months ago

johannesodland commented 10 months ago

The current named timeline ranges all depend on the subject dimensions. If the subject changes dimensions, or the same range is used on another subject, the timing will be different.

Can we define another named timeline range size-independent-to-be-bikeshed, where:

This will let authors set a view-timeline where the timing will not depend on the size of the subject, only the its position across the scrollport. A percentage will represent the subjects center being at a percentage of the view progress visibility range.

.subject {
  animation: linear anim both
  animation-timeline: view();
  animation-range: size-independent 25% size-independent 75%;
}

A small demo of how this range would work compared to cover and contain can be found in this codepen (Chrome only at the moment).

ranges

Background

ViewTimeline timing can be hard to reason about as the timing depend on both the subject and source dimensions. The timing of the 'cover' range will become increasingly slow as the subject size increases, while the 'contain' range will become increasingly fast as the subject size gets closer to the view progress visibility range. A timing that seems reasonable on a desktop screen, might be lighting fast on a small mobile screen.

Creatives coming from other fields such as animation have found this hard to grasp. A range that worked fine on one element, might not work at all on another. I think a named timeline range that depends solely on the subjects position and the view progress visibility range could be useful.

One particular use case is to animate an element across the center of the scrollport, from the center of the element is at 25% of the scrollport until the center of the element is at 75% of the scrollport, independent of the dimensions of the element.

Current workarounds

It is possible to work around the problem using the current spec, but they all have drawbacks:

Use insets It is possible to use inset percentages in stead of a named timeline range to achieve the desired effect.

.subject {
  animation: linear anim both
  animation-timeline: view();
  animation-range: entry-crossing 50% exit-crossing 50%;
  view-timeline-inset: 25%;
}

The animation timing is not dependent on the subject dimensions. A small subject will animate over the same scroll distance as a large subject.

However, this is hard to comprehend and does not convey the authors intent in a good way. It will also require additional calculations if you need to set the view-timeline-inset to account for actual insets due to headers or other elements that cover parts of the view.

Use keyframe selectors It is also possible to use keyframe selectors to achieve the same result.

@keyframes anim {
  25% {
    ...
  }

  75% {
    ...
  }
}

.subject {
  animation: linear anim both
  animation-timeline: view();
  animation-range: entry-crossing 50% exit-crossing 50%;
}

Using keyframe selectors will also work, but it has the drawback that it will prevent authors from reusing existing keyframe effects.

Proposal

Add a new named timeline range as described above.

ydaniv commented 10 months ago

The current named timeline ranges all depend on the subject dimensions. If the subject changes dimensions, or the same range is used on another subject, the timing will be different.

That's the whole point of "view", it's the intersection of the element inside it's scrollport.

Can we define another named timeline range size-independent-to-be-bikeshed, where:

  • 0% progress represents the latest position where the center of the element's principal box coincides with the end edge of its view progress visibility range.
  • 100% progress represents the latest position where the center of the element's principal box coincides with the start edge of its view progress visibility range.

What is "latest" position? "latest" relative to when?

This will let authors set a view-timeline where the timing will not depend on the size of the subject, only its position.

Again, position relative to what? What you want is probably to "dumb down" the model and be able to reduce the element's rectangle to a point. Then you can reason only about the scrollport. So that point needs to be something you can specify, like start/center/end. Then you need to reference that point to the scrollport.

The timing of the 'cover' range will become increasingly slow as the subject size increases, while the 'contain' range will become increasingly fast as the subject size gets closer to the view progress visibility range. A timing that seems reasonable on a desktop screen, might be lighting fast on a small mobile screen.

That's true only if you animate dimensions (width/height) and/or position (top/left/bottom/right/inset). It doesn't change if you animate transform. If you do have to animate dimensions/position then you should either wrap that element in another element and use that as the subject, which isn't ideal, or use scroll-timeline, which also isn't ideal. But I guess that's the trade off for handling such a powerful tool. Again, unless we consider referencing a point on the element, instead of its entire rect.

Creatives coming from other fields such as animation have found this hard to grasp.

Yes, though they're not really our target audience here. Anyhow, I've also come across this quite a lot. I think this is rooted in 2 things:

  1. You didn't have this technology before, so everyone is used to using a library that simply targets a point on the scroll in pixels, like ScrollTimeline. And people are still having a hard time with that.
  2. They're used to libraries that depend on IntersectionObserver and trigger once a point is reached in the viewport.

Also, If you ask them: "how did you do that in After Effects etc.", the usual answer will be: "I created a dummy element to visualize the trigger", which kinds of goes back to using a wrapper element.

I don't really see how manipulating the view-timeline-inset helps here, it simply changes the scrollport's reference.

Bottom line, maybe the current range names are a bit too magical? We could consider having something like entry-start/entry-center/entry-end etc.

johannesodland commented 10 months ago

The current named timeline ranges all depend on the subject dimensions. If the subject changes dimensions, or the same range is used on another subject, the timing will be different.

That's the whole point of "view", it's the intersection of the element inside it's scrollport.

Yes. View progress timelines are for animations that start and end while the element is in view within the scrollport. That is the case here as well. Animation keyframes can be attached to named timeline ranges such as 'cover' and 'contain'. I am merely asking for another named timeline range.

Can we define another named timeline range size-independent-to-be-bikeshed, where:

  • 0% progress represents the latest position where the center of the element's principal box coincides with the end edge of its view progress visibility range.
  • 100% progress represents the latest position where the center of the element's principal box coincides with the start edge of its view progress visibility range.

What is "latest" position? "latest" relative to when?

I am borrowing the wording of the current specification here. That wording handles sticky positioned elements. To quote the spec:

NOTE: For sticky-positioned boxes the 0% and 100% progress conditions can sometimes be satisfied by a range of scroll positions rather than just one. Each range therefore indicates whether to use the earliest or latest qualifying position.

This will let authors set a view-timeline where the timing will not depend on the size of the subject, only its position.

Again, position relative to what?

My wording might not have been clear enough. Apologies. I am referring to the elements position relative to the 'view progress visibility range'. In most cases that will be relative to the viewport or the scrollport, if they haven't been modified by a view-timeline-inset.

What you want is probably to "dumb down" the model and be able to reduce the element's rectangle to a point. Then you can reason only about the scrollport. So that point needs to be something you can specify, like start/center/end. Then you need to reference that point to the scrollport.

I do not want to "dumb down" the model. I think it would be useful with another attachment range to better specify the start and end positions of some of the animations we make.

The timing of the 'cover' range will become increasingly slow as the subject size increases, while the 'contain' range will become increasingly fast as the subject size gets closer to the view progress visibility range. A timing that seems reasonable on a desktop screen, might be lighting fast on a small mobile screen.

That's true only if you animate dimensions (width/height) and/or position (top/left/bottom/right/inset). It doesn't change if you animate transform.

I'm sorry about the wording again. The concern here is not situations where we animate the elements size, but I get how it can seem like that from my wording.

The timing of the 'cover' range will be 'slower' on a larger subject. If we compare the timing of an animation attached to the 'cover' range for two different elements it might be more clear:

This is fine. Sometimes that is what you need. I'm arguing that it's useful to have an attachment range where the animation progresses equally for these two elements.

If you do have to animate dimensions/position then you should either wrap that element in another element and use that as the subject, which isn't ideal, or use scroll-timeline, which also isn't ideal. But I guess that's the trade off for handling such a powerful tool. Again, unless we consider referencing a point on the element, instead of its entire rect.

As I stated in the issue text, there are better workarounds that work with the current spec. We've been using versions of them for years. I do think we can improve the notation for authors.

I don't really see how manipulating the view-timeline-inset helps here, it simply changes the scrollport's reference.

There was a bug in the stylesheet there. It should have said view-timeline-inset: 25%;

If you set an animation range of entry-crossing 50% exit-crossing 50% the animation attachment range will start when the elements center is at the end edge of the scrollport, and stop when the elements center is at the start edge of the scrollport. These are the endpoints of the size-independent animation range I suggested.

Now that the animation attachment range is set to these endpoints, we can use inset to simulate a progress along this animation range. These two would give the same timing:

.before {
  ...
  animation-range: entry-crossing 50% exit-crossing 50%;
  view-timeline-inset: 25%;
}

.after {
  ...
  animation-range: size-independent 25% 75%;
}

Note: I'm not advocating size-independent, I'm sure we can come up with a better name.

Bottom line, maybe the current range names are a bit too magical? We could consider having something like entry-start/entry-center/entry-end etc.

I think the existing names work fine. It's just that they all depend on the subject size, and I'm asking for a new range that is independent of the elements size.

ydaniv commented 10 months ago

This is fine. Sometimes that is what you need. I'm arguing that it's useful to have an attachment range where the animation progresses equally for these two elements.

If you want to sync the animation of multiple elements in different sizes (and/or position) it's as easy as using a single named timeline, and then referencing that timeline in all of these animations.

Alternatively, you could use a duration with a fixed length like:

animation-range: contain 0% contain 500px;

This is fine. Sometimes that is what you need. I'm arguing that it's useful to have an attachment range where the animation progresses equally for these two elements.

You already have the tools to create synchronized animations for the case above. And IMHO they're already quite simple to use as is.

As I stated in the issue text, there are better workarounds that work with the current spec. We've been using versions of them for years. I do think we can improve the notation for authors.

I would really love to see examples of these.

If you set an animation range of entry-crossing 50% exit-crossing 50% the animation attachment range will start when the elements center is at the end edge of the scrollport, and stop when the elements center is at the start edge of the scrollport. These are the endpoints of the size-independent animation range I suggested.

That's a very specific case that can easily be done by using a single named timeline.

In order for different timelines to be "size independent" you simply need to make sure their durations are equal. If you want a single timeline to be "size independent" you can simply set both start and end ranges to reference same point like:

animation-range: contain 0% contain 100vh;

And you can also play with the view-timeline-inset on top of that.

Because I've had the same issues with explaining this model to PMs and designers, I'm really trying to get to the bottom of your issue.

SebastianZ commented 10 months ago

I think what @johannesodland is referring to is to make it easier to specify animation ranges that are based on the size of the scrollport.

So here are four examples where the animation range corresponds to the height of the scrollport:

Four different animation ranges of the size of the scrollport

That includes that any offset is applied to both the end and start of the animation range and percentual offsets also referring to the scrollport's size. This effectively moves the animation range but keeps its size the same. And that makes it independent of the subject's size, in consequence.

So, IIUC, the request is to have a new named timeline range that refers to the scrollport size. @johannesodland Please correct me if I misunderstood your proposal!

Sebastian

johannesodland commented 10 months ago

So, IIUC, the request is to have a new named timeline range that refers to the scrollport size. @johannesodland Please correct me if I misunderstood your proposal!

I think you understand me correctly :) The request is to add a new named timeline range that 'refers to the scrollport size' and thus is independent of the subject's size.

The motivation is to make it easier to specify an animation range with the mentioned properties. If we add a new named timeline range <range> that is independent of the subject size, an animation range such as animation-range: <range> 25% <range> 75% will also be independent of the subjects size.

animation-range

Note that it's currently easy to set up animation ranges that are based on the full size of the scrollport by using entry- and exit-crossing: animation-range: entry-crossing 50% exit-crossing 50%. The goal is to make it easy to map to a part of that range such as animation-range: <range> 25% <range> 75%.

johannesodland commented 3 months ago

@fantasai Thanks for the chat at CSS Cafe. I tried to describe the issue here, although I think Sebastian did a better job of it :)

As suggested, instead of adding a new named range we could add a new margin property to adjust the size of the subject area. This would be similar to how scroll-margin adjust the scroll-snap area.

Then we could use negative margins to negate the size of the subject.

Something like this:

@keyframes --anim {
  from {...}
  to {...}
}

.subject {
  animation: linear --anim both;
  animation-timeline: view(0 / -50%);
  animation-range: 25% 75%;
}

/* Or through longhands */
.subject {
  view-timeline-name: --timeline;
  view-timeline-inset: 0;
  view-timeline-subject-margin: -50%; /* name up for bikeshedding */

  animation: linear --anim both;
  animation-timeline: --timeline;
  animation-range: 25% 75%;
}  
ydaniv commented 3 months ago

So what you're asking, IIUC, is to reduce the subject from a line to a point, and then have the range match the progress of that point inside the scrollport. And that -subject-margin property you're asking is a way to customize where to place that point on that line.

johannesodland commented 3 months ago

So what you're asking, IIUC, is to reduce the subject from a line to a point, and then have the range match the progress of that point inside the scrollport. And that -subject-margin property you're asking is a way to customize where to place that point on that line.

Right :)

Adding a -subject-margin property would provide a way to reduce the subject to a point, so that the resulting timeline only "depends" on the size of the scrollport.

The benefit of adding a -subject-margin property is that it makes it possible to control where on the subject that point is. It might be better than the initial suggestion with a new named timeline range, where that point always would be in the center of the subject.

ydaniv commented 3 months ago

It's not just a matter of ergonomics, we also need to consider the model here. I also think we can provide some more configuration options here and extend the proposal from specifying a single point to allow specifying 2 points: one for the timeline's start point and another for its end.

On one hand, if this is a property of view-timeline it could be reused on every animation that's using it. The question is how will these 2 points interact with the existing ranges, since they're all defined from the start-edge of the subject to it's end-edge (unless the subhect is larger than the scrollport). I suppose if they're the same point on the subject, entry and exit shrink the timeline to a single point, while contain and cover contract it to the progress of that point through the entire scrollport. Regarding syntax, "inset" is already taken, and "offset" would be to confusing, especially with ViewTimeline.startOffset/endOffset. Maybe something like view-timeline-subject-inset and ViewTimeline.subjectInset? Also, the shorthand is a bit awkward with -inset. Perhaps using a / may improve it?

OTOH, if we keep view-timeline as is and extend the syntax of animation-range, we can't share this timeline of a single point on a subject, at least until we have GroupEffects (: Regarding the syntax, I don't think we need a new range here, but rather allow pairs of <length-percenrage> for animation-range-start and animation-range-end, so that the first value is relative to subject's start-edge, and the second is relative to scrollport's `start-edge.

So to summarize our options:

  1. Extending view-timeline:
    • Can share across different animations
    • Can't be used with named ranges, only length-value - like scroll-timeline
    • Can work with existing syntax

Example:

.source {
  view-timeline: --source-view y / 50%; /* define the timeline to be the progress of the center of the subject through its nearest scrollport */
}
  1. Extending animation-range:
    • Can't be shared across different animations (until we have GroupEffect)
    • Avoids issue with named ranges
    • Probably requires a new unique syntax

Example:

.target {
  animation-timeline: view();
  animation-range: 50% 0% 50% 100%; /* starts when subject's center enters viewport and ends when it leaves it */
}

Note you have to use 4 values, because using simply 50% 0% 100% will mean that the 100% is converted to cover 100%. I suppose we could try considering new keywords for a specific point on the subject that would make it more readable.