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] currentTime when scroll range is 0 (again) #7778

Closed
bramus opened this issue Sep 22, 2022 · 16 comments
Closed

[scroll-animations-1] currentTime when scroll range is 0 (again) #7778

bramus opened this issue Sep 22, 2022 · 16 comments
Labels
Closed Accepted by CSSWG Resolution Commenter Satisfied Commenter has indicated satisfaction with the resolution / edits. scroll-animations-1 Current Work

Comments

@bramus
Copy link
Contributor

bramus commented Sep 22, 2022

In #7401 we resolved on making the timeline inactive in the absence of a scroll range.

Playing around with the implementation in Chrome Canary (with Experimental Web Platform Features flag), I’m starting to doubt this resolution.

From a UX perspective, one could argue if there’s a need to show a full progress bar in the second case or not. However, I can imagine other situations – e.g. where something fades in as you scroll down – that would need to be visible in case of no scroll range.

Maybe we should revisit?

@bramus bramus added the scroll-animations-1 Current Work label Sep 22, 2022
@bramus
Copy link
Contributor Author

bramus commented Sep 22, 2022

💡 Maybe animation-fill-mode could affect what needs to be done?

@ydaniv
Copy link
Contributor

ydaniv commented Sep 23, 2022

Yes, actually that's correct!
That's currently what all libraries do, and it also corresponds better to time-based animations with duration of 0.

@flackr
Copy link
Contributor

flackr commented Oct 7, 2022

Which end of the animation would you fill? An animation linked to a 0 scroll range is conceptually both before the beginning (first keyframe) and after the end (last keyframe).

@bramus
Copy link
Contributor Author

bramus commented Oct 7, 2022

Last

@flackr
Copy link
Contributor

flackr commented Oct 7, 2022

I don't think I agree with the expectation here. When a scroller shrinks from scrollable to not scrollable the animation will arbitrarily jump to the end even if it was previously at 0 progress (and is still at 0 scrollTop / scrollLeft), which to me seems surprising. I'm not sure how you can decide that one end of the scroll is the preferred state. You can set the style you want when there is 0 scroll range by setting the underlying style (not in the animation).

Making this configurable by animation-fill-mode is I think not feasible. The timeline current time is an input to the animation, and each associated animation can have a different fill mode. I suppose we could always have the timeline output 100% if there is no scroll and have animations local time treat it as an exclusive 100% so without animation-fill: after the effect would still be inactive, but I worry that this may be overindexing on your read progress indicator use case.

@ydaniv
Copy link
Contributor

ydaniv commented Oct 10, 2022

When a scroller shrinks from scrollable to not scrollable the animation will arbitrarily jump to the end even if it was previously at 0 progress (and is still at 0 scrollTop / scrollLeft), which to me seems surprising

I don't think it's surprising, if something causes duration of the animation to shrink/expand then jumps in progress calculation should be well expected.

I'm not sure how you can decide that one end of the scroll is the preferred state

That is true.
You can roughly categorize most scroll-linked animations into 3 groups:

  • Entry effects
  • On-going effects
  • Exit effects

And it's easy to find examples for each category to have a different desired frame to fill. Nevertheless, this is still something quite common that could give a lot of benefit to authors.

Let's take on-going effects for instance:
You could have a filter: grayscale() going from 100% to 0%, in which you may choose to fill the 100% probably.
Another case is a filter: hue-rotate() going from -180deg to 180deg, in which you may choose to fill to 50% progress.

This is also not possible to cover simply with animation-fill-mode.

So, perhaps we can research this further, maybe introduce a new property for the Timeline, around which the UA should expand the interpolated range? So that we can also maintain continuity at the same time?

@ydaniv
Copy link
Contributor

ydaniv commented Oct 10, 2022

No, on second thought, scratch the above, it won't be possible to maintain continuity in most cases.
Perhaps what @flackr wrote above, using the underlying style as fallback when the timeline is inactive, i.e. duration <= 0, that's probably the best shot here.

Though, we might want, in this context, rethink a non-default clamping of duration as suggested in #7432 .

@bramus
Copy link
Contributor Author

bramus commented Nov 10, 2022

I don't think I agree with the expectation here. When a scroller shrinks from scrollable to not scrollable the animation will arbitrarily jump to the end even if it was previously at 0 progress (and is still at 0 scrollTop / scrollLeft), which to me seems surprising.

I see. When the user hasn’t scrolled the scroller, suddenly jumping to the end state seems weird indeed. In the progress bar example it would go from no progress bar at all to a full progress bar upon becoming not scrollable. It’s better to not have it jump from 0% to 100%.

Additionally, when the user has scrolled a little bit the progress bar would grow from 75% (for example) to 100% width as the scroller grows, to then have it disappear when the scroller becomes not scrollable. This behavior is similar to how scrollbars work: these also disappear when there’s nothing to scroll.

I’m OK with leaving this “as is”. Maybe in the future we’ll hear of use cases where the other behavior is needed, and can then come up with a solution.

@flackr
Copy link
Contributor

flackr commented Nov 10, 2022

I was thinking about this a bit more, and while I still think that for ScrollTimeline the current behaviour is correct, you can construct a view timeline whose animatable range approaches a known value as the scroll range goes to 0.

For example, I put together a demo (with polyfill is a bit harder to play with since it doesn't currently respond to position changes) where as you grow the scrollable area the range of the animation approaches a particular value. I think when the range is a single value I would expect that value to be the current progress rather than becoming inactive. I think this would require an edit to the previous text.

Interestingly, the chromium implementation already does what I would expect in this case, so I think we may just need to allow it from the spec side. The particular text would probably be easier to write if the spec had the current time formula. as we could then determine the exact conditions under which it approaches a known value when the scroll range goes to 0.

@bramus
Copy link
Contributor Author

bramus commented Nov 14, 2022

Good call on splitting it up between ScrollTimeline and ViewTimeline - makes sense.

So for ScrollTimeline the animation would become inactive when there’s no scroll estate, whereas for ViewTimeline it would retain its last active frame (with respect to the set fill mode)? (Most likely I’m not using the correct terms here)

@flackr
Copy link
Contributor

flackr commented Nov 14, 2022

So for ScrollTimeline the animation would become inactive when there’s no scroll estate, whereas for ViewTimeline it would retain its last active frame (with respect to the set fill mode)? (Most likely I’m not using the correct terms here)

It's actually not specific to ViewTimeline or ScrollTimeline, but based on whether there is a known value at 0 scroll range (scrollHeight = scrollportHeight). The formula for a ScrollTimeline is (probably should add this to the spec):

current_time = (offsetTop - startOffset) / (endOffset - startOffset)

For the default case, startOffset = 0, and endOffset = scrollHeight - scrollportHeight, so this simplifies to just:

current_time = offsetTop / (scrollHeight - scrollportHeight)

As endOffset approaches 0, and with offsetTop bounded between 0 and endOffset, the value of currentTime is only stable if offsetTop is 0, otherwise it rapidly approaches 100%.

However, if start offset is not 0 or end offset is not based on the scroll range (proposed by #7575), then the divisor becomes stable. e.g. for startOffset = 100 and endOffset = 200:

current_time = (offsetTop - 100) / (100)

For view timelines, we calculate a start and end offset that is not clamped to the scroll range (#7432). The formula for the cover range of a particular element (for vertical directions to try to keep things shorter) is:

current_time = (scrollOffset + scrollportHeight - elementOffset) / (elementHeight + scrollportHeight)

The formula again, doesn't become unstable when scroll range goes to 0 because it doesn't depend on it.

However, for the contain range the formula is:

current_time =
    (scrollOffset + scrollportHeight - elementOffset - min(elementHeight, scrollportHeight)) /
    max(scrollportHeight - elementHeight, elementHeight - scrollportHeight)

If that element spans the entire scrollable content, elementOffset = 0, elementHeight = scrollHeight, making this:

current_time = scrollOffset / (scrollHeight - scrollportHeight)

Thus you have the exact same calculation as a vanilla ScrollTimeline.

By having the formulas in the spec, we could simply say that the timeline becomes inactive if the denominator is 0.

@ydaniv
Copy link
Contributor

ydaniv commented Nov 15, 2022

SGTM!

I think the formula should also include insets, just for sake of completeness, so we get:

scrollport_height = scrollportHeight - (view_timeline_inset_start + view_timeline_inset_end)

So that changing the insets should also change that specific value.

Also note that insets may also affect the formula for contain and the case for timeline inactivity.

@flackr
Copy link
Contributor

flackr commented Nov 15, 2022

Thanks @ydaniv, absolutely insets should be part of the formula!

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [scroll-animations-1] currentTime when scroll range is 0 (again), and agreed to the following:

  • RESOLVED: Scroll animation is inactive when denominator is 0
The full IRC log of that discussion <Rossen_> https://github.com//issues/7778#issuecomment-1313875566
<dael> flackr: Previously we defined scroll timeline to be inactive when scroll range was 0 to avoid divide by 0. This is a slight ammendment b/c many cases where you don't end up with division by 0. Examples in prop
<dael> flackr: Let's define calc and say animation is inactive if denominator is 0.
<dael> Rossen_: Thoughts or objections on this one? Scroll animation is inactive when denominator is 0
<dael> RESOLVED: Scroll animation is inactive when denominator is 0

@fantasai
Copy link
Collaborator

Edited in as 4cf807e ; @flackr lmk if there's any problem, otherwise close out the issue if it's correct?

@flackr flackr added Commenter Satisfied Commenter has indicated satisfaction with the resolution / edits. and removed Commenter Response Pending labels Mar 23, 2023
@flackr
Copy link
Contributor

flackr commented Mar 23, 2023

Looks good to me, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Closed Accepted by CSSWG Resolution Commenter Satisfied Commenter has indicated satisfaction with the resolution / edits. scroll-animations-1 Current Work
Projects
None yet
Development

No branches or pull requests

6 participants