-
Notifications
You must be signed in to change notification settings - Fork 642
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] Support series of scroll offsets #4912
Comments
This is an interesting idea which I am in favor of. I can see how multiple scroll offsets can simplify the API and is more ergonomics due to the fact that it can be paired with multi-frame keyframe effects. Follow the processing logic for keyframes here are some ideas on how to spec and implement this:
/cc @flackr @ogerchikov @birtles for awareness and in case they have suggestions here. |
This comment summarizes offline discussion between @majido and @ogerchikov regarding handling unsorted offsets. Below are presented options:
We feel that options 1 and 4 are hard to reason about and debug, option 2 is not practical since layout can change. For initial implementation we feel that option 3 is the most desirable as it lets unsorted offsets to exist and resolved current time to be produced once/if the offsets become sorted. It also allows for future modifications towards options 1 or 4 if practical needs arise. |
This was discussed in last F2F sync. @birtles suggested an interesting approach based on clamping the offending values. The clamp value could be a middle value and neither of the two offending value. This can lead to an outcome that is closer to author intent. Consider this example In the context of reveal/unreveal example a realistic case is when the item we want to reveal is larger than the viewport which would produce such a situation. The suggested clamping approach will cause the reveal to continue for 50% of total animation and unreveal to start to the remainder. While we never reach opacity 1 but it seems a better option than alternatives. The consensus was that this looks like a promising approach instead of returning unresolved time. Action Items:
|
Just a correction, my understanding is that we would in fact reach opacity 1 at exactly scroll offset 250. It's just that the time to get to opacity 1 would be only 150px instead of the requested 200px on either end. |
My expectation is you would never get to opacity 1. That seems to me personally to be more in line with what the author intended rather than speeding up the effect. |
Thanks for the clarification, I agree this is probably more in line with the developer intention, however I think this isn't equivalent to [100, 250, 250, 400], because it loses the information that 250 is only 2/3 of the way to the second / third keyframe. Perhaps we should keep the conflicting scroll offsets in the calculated offsets and define a procedure for determining the current offset which applies this logic, e.g. something like If offset i + 1 is less than offset i, find the first offset j which is greater than or equal to offset i. If the scroll offset falls between offset i and offset j then the resulting animation offset uses the pair [i, i + 1] or [j - 1, j] whichever of i or j is closer to the current position. |
Earlier in this thread I speculated if we should allow "offsets" for these scrollOffset similar to how we have these on keyframes. My conclusion then was that perhaps its complexity is not justified but I wonder if having offset makes achieving the desired clamping behavior without unnecessary speed up actually simpler. Consider if we actually have implied offsets for the current example. So The logic for clamping then detects any series of offsets that don't match the expected order then it clamps both the values and their offsets to the middle point. So in the example case it will turn it into The advantages are:
|
While at it perhaps it is also worth bikeshedding I think we should not include The only other good name that I have come up with so far is |
I'm confused, isn't the map we're trying to build scroll offset to animation offset, not the reverse? My expectation would be that if we include animation offsets [100, 300, 200, 400] would produce {100 => 0, 250 => 0.22, 250 => 0.78, 400 => 1} where the 0.22 and 0.78 are the relative proportion of those interpolations that would be covered (e.g. 66% of the 33% animation progress achieved at offset 250). This clearly shows that we're skipping the entire animation between 22% and 78%. If we strictly use the midpoint of the overlapping animation offsets we might in fact change the speed of the effect, consider: |
@flackr you are right that I confused the offset calculation 🤦 . But your interpretation of it is correct. Basically adjust the computed offsets to maintain the speed. BTW here is another edge case to consider when writing up the algorithm for this: |
We've been discussing alternate cases and have come to the conclusion that while the magic middle checkpoint makes a lot of sense for the one use case, it falls apart when you look at more complex use cases such as the one Majid mentioned above.
I think that there's two takeaways from the above: |
Naming scroll offsets threshold can be misleading since threshold already participates in element offset definition. How about ScrollTimeline.ranges instead? |
I feel as though it might be better to stick with scrollOffsets, since it's completely clear what this means. |
Thanks @ogerchikov for making me realize that the thresholds also has a conflict! On naming decision, I tried to summarized my thoughts based on latest suggestion. My perspective is the the CSS syntax of scroll-timeline is the one that will be used more often so we should optimize for that if there is no good option for both. And so far nothing proposed is great in both JS and CSS. With this premise, my initial concern about the property name conflicting with others is not as important since we don't spell out either @scroll-timeline {
scroll-offsets: 10% 90%;
} Based on above my preference would be:
Another alternative is to have different names in css and js but I like to avoid that. Here are all the suggested options in multiple real examples in JS and CSS with my opinion on pros and cons. offsetsPros:
Cons:
const scrollTimeline = new ScrollTimeline({
source: scroller,
offsets: [CSS.percent(20), CSS.percent(50), CSS.percent(80)]
});
const scrollTimelineWithOffset = new ScrollTimeline({
source: scroller,
offsets: [{value: CSS.percent(20), offset: 0} , {value: CSS.percent(50), offset: 0.5}, {value: CSS.percent(80), offset:1}]
});
const scrollTimelineWithElements = new ScrollTimeline({
source: scroller,
offsets: [{target: image, threshold: 0}, {target: image, threshold: 0.8}, {target: image, threshold: 1}]
});
console.log(scrollTimeline.offsets); @scroll-timeline basic {
source: selector(#scroller);
offsets: 20, 50, 80;
}
@scroll-timeline with-offsets {
source: selector(#scroller);
offsets: 20 0%, 50 50%, 80 100%;
}
@scroll-timeline with-elements {
source: selector(#scroller);
offsets: selector(#image) 0, selector(#image) 0.8, selector(#image) 1;
} scrollOffsetsPros:
Cons:
const scrollTimeline = new ScrollTimeline({
source: scroller,
scrollOffsets: [CSS.percent(20), CSS.percent(50), CSS.percent(80)]
});
const scrollTimelineWithOffset = new ScrollTimeline({
source: scroller,
scrollOffsets: [{value: CSS.percent(20), offset: 0} , {value: CSS.percent(50), offset: 0.5}, {value: CSS.percent(80), offset:1}]
});
const scrollTimelineWithElements = new ScrollTimeline({
source: scroller,
scrollOffsets: [{target: image, threshold: 0}, {target: image, threshold: 0.8}, {target: image, threshold: 1}]
});
console.log(scrollTimeline.scrollOffsets); @scroll-timeline basic {
source: selector(#scroller);
scroll-offsets: 20, 50, 80;
}
@scroll-timeline with-offsets {
source: selector(#scroller);
scroll-offsets: 20 0%, 50 50%, 80 100%;
}
@scroll-timeline with-elements {
source: selector(#scroller);
scroll-offsets: selector(#image) 0, selector(#image) 0.8, selector(#image) 1;
} thresholdsPros:
Cons:
const scrollTimeline = new ScrollTimeline({
source: scroller,
thresholds: [CSS.percent(20), CSS.percent(50), CSS.percent(80)]
});
const scrollTimelineWithOffset = new ScrollTimeline({
source: scroller,
thresholds: [{value: CSS.percent(20), offset: 0} , {value: CSS.percent(50), offset: 0.5}, {value: CSS.percent(80), offset:1}]
});
const scrollTimelineWithElements = new ScrollTimeline({
source: scroller,
thresholds: [{target: image, threshold: 0}, {target: image, threshold: 0.8}, {target: image, threshold: 1}]
});
console.log(scrollTimeline.thresholds); @scroll-timeline basic {
source: selector(#scroller);
thresholds: 20, 50, 80;
}
@scroll-timeline with-offsets {
source: selector(#scroller);
thresholds: 20 0%, 50 50%, 80 100%;
}
@scroll-timeline with-elements {
source: selector(#scroller);
thresholds: selector(#image) 0, selector(#image) 0.8, selector(#image) 1;
} rangesPros:
Cons:
const scrollTimeline = new ScrollTimeline({
source: scroller,
ranges: [CSS.percent(20), CSS.percent(50), CSS.percent(80)]
});
const scrollTimelineWithOffset = new ScrollTimeline({
source: scroller,
ranges: [{value: CSS.percent(20), offset: 0} , {value: CSS.percent(50), offset: 0.5}, {value: CSS.percent(80), offset:1}]
});
const scrollTimelineWithElements = new ScrollTimeline({
source: scroller,
ranges: [{target: image, threshold: 0}, {target: image, threshold: 0.8}, {target: image, threshold: 1}]
});
console.log(scrollTimeline.thresholds); @scroll-timeline basic {
source: selector(#scroller);
ranges: 20, 50, 80;
}
@scroll-timeline with-offsets {
source: selector(#scroller);
ranges: 20 0%, 50 50%, 80 100%;
}
@scroll-timeline with-elements {
source: selector(#scroller);
ranges: selector(#image) 0, selector(#image) 0.8, selector(#image) 1;
} |
If I'm not mistaken this issue may be closed as #5803 got merged. Or are there any other outstanding things that need to be tackled? On a side note: the implementation in Chromium just caught up with the changes outlined in #5803 :) — https://bugs.chromium.org/p/chromium/issues/detail?id=1094014#c9 |
Agreed, that looks great, thanks for the heads up! |
Inspired by the proposal to allow Element-based offsets, I propose we can simplify the
startScrollOffset
andendScrollOffset
props into a singlescrollOffsets
array.This would allow us to leverage
KeyframeEffect
's ability to accept more than two keyframes in an animation.Example
The "Example 1. Reveal / Unreveal" example in the above ticket shows the definition of two
ScrollTimeline
s, like this:To create one conceptually unique effect (fade in while on screen), we create two timelines, two effects, and two animations.
Instead, we can half this to just one, with a single
scrollOffset
array:Taken from @majido's example, this would change the above to:
The text was updated successfully, but these errors were encountered: