-
Notifications
You must be signed in to change notification settings - Fork 710
Element-based start and end offsets #4337
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
Comments
@stephenmcgruer @birtles I will be at TPAC and will be more than happy to discuss this F2F. |
Adds an intersection based offset polyfill based on https://github.com/WICG/scroll-animations/issues/51 which allows the start and end offsets to be calculated based on the position of a target element. Also adds a demo indicating the utility of these target based effects.
I put together a rough polyfill / demo of most of the JS API - notably missing rootMargin: One other deviation in that demo is that it uses clamped start and end offsets. If I follow the proposed unclamped behavior outside the scroll range then many of the effects become harder to calculate how to correctly apply: I initially found the edge parameter confusing but in retrospect the ability to make animations that are based on an element's introduction into one edge of the screen make sense. |
Interestingly though, clamping makes the header fly-in animations difficult as the headers which start on screen have a start and end scroll offset of effectively 0, making the current time undefined. The polyfill currently special cases that to time 0 to avoid the division by 0. What should the behavior be when they work out to the same value? |
I went through this proposal today. It looks really good. I'm especially glad to see the CSS syntax too. My initial reactions are:
|
@flackr the polyfill and demos are awesome specially that it is started to identify some good insights for clamped/unclamp behavior. On the edge case where both start == end == current scroll offsets then per current spect we will be in otherwise clause of step 4 here which should return effective time range and not zero (I am papering over the fill-mode issue). This may be more reasonable than zero but this needs more thinking. |
Thanks for the feedback @birtles. Here are some initial thoughts on the issues you mentioned:
In Lyon face-to-face there was a discussion on this topic. And we resolved to keep ScrollTimeline focused on scroll-linked effect and have a separate/simpler concepts to trigger transitions/time-based animations that are simpler. Notes from that discussion are here For scroll-triggered time-based animations one proposal was to maybe do an
These are not uni-directional. It is the same model as current ScrollTimeline which is bi-directional. Rob's demo page can show this. Just load the page, scroll half way and refresh which should load the page in the previous scroll position. The animation will be at the state that it was before which is what I would have expected 👍.
I hope the current proposed solution is enough but this needs more explorations. Ideas welcome! BTW that the current spec has some wording on avoiding layout circularity but it is focused on the "current time". I think we may need a slightly different approach for start and end bounds. |
Ok, I need to re-read it I guess. I don't understand why two timelines are needed for the first example if this is bidirectional. Why is one not enough?
I think if we're adding this much API surface area, we should be able to handle scroll-triggered time-based animations too. Let's work out how to do that. |
Here is a diagram that may help with understanding the first example better. Basically there are two timelines involved because the effects involve two different ranges:
|
Thank you, that helps a lot! |
Spoke to @heycam about this and realized we could use vanilla |
The CSS Working Group just discussed
The full IRC log of that discussion<emilio> Topic: ScrollTimeline<emilio> majidvp: Last f2f I explained the 2 big issues that remained, the css syntax and the problem that the spec only accepts concrete scroll offsets and such and most use cases rely on viewport offsets and such <emilio> ... so we got lots of feedback from devs that it's hard to compute the right offsets <fantasai> basically, same problem as scroll-snap had in the beginning... <emilio> ... so we want to propose some changes to scroll timeline to make the scroll offsets not specified but match intersection of boxes or such <emilio> majidvp: flackr has done a polyfill for that and the api <emilio> ... what we're proposing here is specifying offsets in terms of intersection observer semantics <heycam> q+ <emilio> ... which is start and end of animation as intersection observer offsets <emilio> ... just one-dimensional rather than two-dimensional <emilio> majidvp: [goes through the proposal in https://github.com/WICG/scroll-animations/issues/51] <emilio> github: https://github.com/WICG/scroll-animations/issues/51 <emilio> majidvp: we're also proposing a function-like syntax <emilio> ... but let me show demos <emilio> majidvp: [goes through https://flackr.github.io/scroll-timeline/demo/parallax/] <emilio> majidvp: [goes back to the proposed css syntax] <emilio> ... there are some open questions like how to fix the circularity in the case layout moves the element while animation <emilio> ... proposal is to freeze the offset when the animation starts <heycam> q- <emilio> ... also how intersections are computed and such <emilio> ... these are open questions that we're trying to work through <emilio> ... not proposing concrete solution <emilio> ... happy to answer questions / feedback / concerns <emilio> smfr: I like the way it generally looks, and I like the IntersectionObserver thing <emilio> ... seems much more natural <emilio> ... can you do something like a spinner that stops as soon as soon as you scroll away? <emilio> majidvp: ScrollTimeline should not solve that, you need a trigger for that... <emilio> ... I don't wanted to fix that use case here, but maybe a `:visible` pseudo-class or a CSS intersection observer like syntax <emilio> iank_: you can polyfill that already with intersection observer, I think it's nice to keep it focused <emilio> dino: I think this would be a simple addition now that we have the range to address this use case <emilio> smfr: there's also the case where you stop the animation but let it run to complete a cycle <emilio> ... so that it comes back into the viewport in a good position <emilio> majidvp: may be addressable with the range <emilio> smfr: another piece of feedback is that it seems that the css api is getting a bit out of control <emilio> ... I'd be fine with just a JS api <emilio> majidvp: that's the opposite of the last F2F discussion, but it's fine for me... <heycam> heycam: the small additions to the Intersection Observer model, they sohuld just be added to Intersection Observer itself <emilio> majidvp: I think they should be added to the spec even if they're not web-exposed. <emilio> heycam: it'd be nice specially if you don't solve the time-based viewport-triggered animation <emilio> majidvp: I _think_ you can compute that with the current IntersectionObserver given it provides the intersection area <emilio> Rossen_: Looks awesome, what are you asking from us? <emilio> majidvp: confirmation of general direction would be great <emilio> ... may be nice to bring into csswg-drafts, though may not be that important <emilio> Rossen_: I think we could do that <emilio> smfr: where does web-animations live? <emilio> birtles: CSS <emilio> RESOLVED: moved scroll-timeline into csswg-drafts |
Thanks, I've updated the polyfill to properly implement fill modes, and identified a potential issue from the web animations api #4323. As for fill modes, it almost seems like the timeline should always fill, except that it should fill with a value which doesn't make the animation current so that we use the effect's fill behavior. Unfortunately, such a value doesn't exist for the before state. I filed #4325 to discuss this further. Cheers! |
Note that your examples (and indeed my polyfill since it followed the examples) use thresholds in the range [0, 100] but the IntersectionObserver spec says that thresholds should be in the range [0, 1.0]. I've updated the polyfill and demos. |
As I was doing a prototype implementation of this idea in Chromium, I realized that I have not made this clear that the target for element-based offset need to be a descendant of the scroll timeline source. Otherwise finding an scroll offset that corresponds to intersection does not make much sense. We just have to make this clear when specifying this proposal. Note that a similar restriction exists for Intersection Observer as well:
If author provides a non-descendant targets we can throw on construction, or simply never resolve these to a concrete scroll offset. The IntersectionObserver takes that latter approach and simply never delivers an intersection record for such cases. |
Another interesting edge case is when target element (or scroll source?) does not have a layout box. We should handle this case gracefully as well. Looking at InteresectionObserver, it invokes
For IntersectionObserve, this means an empty intersection which probably safe. For ScrollTimeline have to check if a similar solution works or perhaps we should be more explicitly handling this case as opposed to relying on empty rects. |
Per CSSWG resolution [1], we graduated this specification from WICG to CSSWG a while back. So this is an official ED spec for CSSWG. The status and level are updated to reflect this. [1] #4337 (comment)
Add basic definition for Element-based offsets Major changes: - Introduce concept of "scroll timeline offset" that can be container-based (existing concept) and element-based (new concept). - Add IDL for the new offset type and use it. - Define the process for each offset type to be resolved into an effective scroll offset. - Update current time calculation to resolve offsets and use the effective values. - Add basic diagram to show the behavior visually for a simple example Minor changes: - Rewrap lines to fit in 80 chars - Trim end-of-line whitespace - Clarify some definitions TODO (as follow ups): - Define threshold for element-based offset. - Add css syntax for element-based offsets. - Add more examples.
Add css syntax to for element-based offsets. Fixes #4337. The element-based syntax is simply applied when the value starts with `selector(#id)` with the following characteristics: - `selector( <<id-selector>> )` is required and is expected to be the first value. - both edge and threshold are optional can can be provided in any order. I followed some of the ideas mentioned by @tabatkins in #4348 to to get to a more ergonomic css function syntax. In particular there is no comma and the optional params can be in any order. Note that unlike #4348 we are not adding a function syntax.
EDIT: The following below is an implementation bug on Chrome's end, as per this tweet. In the OP I see an example that uses two @scroll-timeline blocks. @timeline reveal-scroll-timeline {
timeline-type: scroll;
timeline-source: element(scroller) ;
scroll-direction: block;
/* start when target has entered scrollport */
scroll-offset-start: intersection(element(:animation-target), start, 0);
/* end when target is fully within scrollport */
scroll-offset-end: intersection(element(:animation-target), start, 100);
}
@timeline unreveal-scroll-timeline {
timeline-type: scroll;
timeline-source: element(scroller) ;
scroll-direction: block;
scroll-offset-start: intersection(element(:animation-target), end, 100);
scroll-offset-end: intersection(element(:animation-target), end, 0);
}
@keyframes reveal {
from { opacity: 0;}
to { opacity: 1;}
}
@keyframes unreveal {
from { opacity: 1;}
to { opacity: 0;}
}
.image {
animation-name: reveal, unreveal;
animation-duration: 1000;
animation-timeline: reveal-scroll-timeline, unreveal-scroll-timeline;
animation-fill-mode: both;
} Is that still allowed (albeit with an updated syntax)? It's that I've been fiddling with it in this CodePen demo (Chrome Canary with “Experimental Web Platform Features” enabled required), and cannot seem to get that to work. Don't know if this is because the implementation is still in development, or if the spec disallows it. If it's the latter then a note on this in the spec would be needed (or I overlooked it). |
A very common usage pattern for scroll timeline is animating items as they enter or exit the scrollport (or viewport). This was identified as a shortcoming of the current API and it is explained in details here
This issue tries to propose an extension in the ScrollTimeline API to help address this shortcoming. This builds on top of our earlier proposal here.
Proposed Design
Allow scroll timeline's start and end offsets to be declared in terms of elements on the page. More accurately as a single intersection threshold (with a similar semantic as IntersectionObserver) between scroll timeline's scroll source and another element. Note that while the target element is often the animation target itself but this is not necessary.
Why Intersection Semantics and IntersectionObserver
We believe most common use cases can map easily to an intersection point which is simple to express and understand. There are several examples below that demonstrate this more concretely. Assuming intersection semantic is the right one then it is natural to use Intersection Observer which is a primitive that was designed for this very exact use case. By using Intersection Observer semantics which are well understood, specified and documented, we keep the platform consistent, make the feature potentially easier to implement, and also make it easier to polyfilling as well.
Additions to the IntersectionObserver model
To make this work for ScrollTimeline we need a few additions to the Intersection Observer model but they seem to be reasonable and small.
One Dimensional Intersection: Intersection observer calculates intersection (and thus thresholds) on 2d plane but for scroll we want one dimensional intersection. I believe this is easy to define and introduce to intersection observer model.
Edge Dependency: Intersection observer does not differentiate between start and
end edges and produces intersection entries regardless of where the intersection occurs. The actual observer callback can then detect this based on the info that is available in the entry. For scroll timeline use cases we want to differentiate when something intersect at the start or end edge. Again this seems likely to be a simple addition to Intersection Observer model.
Proposed API
Semantic for intersection based offsets:
edge
andmode
here represent the new addition to the Intersection Observer model.IMPORTANT: It is actually not required for implementations to create an instance of intersection observer. In fact they can and should precompute the exact scroll offsets that would result in the given thresholds and use those as concrete offsets. Though such pre-computed offsets gets invalidated and need to be recomputed whenever intersection observer would have been invalidated.
Note that “0” threshold in IntersectionObserver signals signal transition from not-intersecting to intersecting if the target and root become edge-adjacent, even if the actual overlap area is zero pixels. This matches what we want as well. It may however be necessary to differentiate between the case when zero is “intersecting” or “non-intersecting” which is exposed by Intersection observer as isIntersecting attribute.
Optional scrollRange
We can also introduce
scrollRange
that could be used to declare one offset in terms of the other using,start + range = end
. This can be handy in some cases.CSS Syntax
We are assuming that we use and @rule based css syntax (See this issue for further details). In that case the intersection based offset can be implemented in the form of a css function:
Initially, it may be enough for
<element()>
to only support #ID selector and also a special syntaxelement(:animaton-target)
that selects the animation target itself. Later this can be expanded to support more complex selectors.Here is an example of how it can be used:
Examples
The following examples are meant to help demonstrate the ergonomics of the API is some common scenarios.
Example 1. Reveal / Unreveal
An image that goes from transparent to opaque when it enters scrollport and in reverse when it leaves scrollport.
html structure:
reveal animation:
Note: If we had scrollRanger we could specify scrollRange: 100% of image height instead.
unreveal animation:
Note that if element is larger than scrollport the two animations may overlap. Animation composite can be used to determine how this works.
Here is these effect expressed using web animation API in Javascript:
Here is how the same thing expressed in CSS:
Here is a diagram that demonstrates how the two timelines and their associated animations start and end.
Example 2. Progress bar left to right as we scroll a single page
Consider a document that consists of several sections and we want to show a simple progress bar that goes from 0 -> 100% when user scrolls through each section. This examples shows how intersection target may be different from animation target.
html structure:
Progress animation:
Here is how the same thing expressed in CSS:
Example 3. Image scales up to be fully centered
An image that starts growing and reaches its maximum size once it is fully centered in the viewport. For example see iphone 11 page
html structure:
Scale animation:
Note: this is a case where
scrollRange
is useful. Here we want end offset to be relative to start offset but it is not easily specified as an intersection. With a simple addition of scrollRange we can define an intersection based start and then specify the scroll range for which the animation should remain for. It is perhaps possible to do a similar thing with an extra element that is positioned to be in the center of the page but that is not as ergonomic.Open Questions
Circularity
What happens if the animation moves the elements used in the target that could cause the scroll bounds to change which then alters the animation. This can lead to circular dependency between animation and its triggers.
One way to avoid this circularity is to freeze start/end offset when timeline is active/inactive (and thus animating). Here is the change that can potentially achieve this:
When a timeline is active has an active animation it ignores any start observer notifications and uses its existing concrete start offset.
When a timeline is inactive it ignores any end observer notifications and uses its existing concrete end offset.
TODO: Do we also need to assume that we calculate intersections based on last frame’s layout? Otherwise in the same frame we may have a situation where: 1) compute intersection -> 2)trigger animation which invalidates layout -> 3) in new layout we no longer intersect.
Intersections are calculated based on previous frame
The operation of IntersectionObserver requires the document lifecycle is complete (clean layout/paint (?)). So its callbacks are invoked in the next frame after the fact. Is this sufficient for start/end offset for scroll-driven effects?
Note that to avoid circularity we already ignore the animation output itself. This puts additional restriction that we ignore anything that has occurred since the last frame (e.g., event handlers dirtying the layout, etc.).
I believe this a common problem for any element-based approach regardless of how the concept of intersection is declared.
Dealing with offsets outside scroll range
Sometimes the start and end offsets are outside the scroll range. For example consider the case when the animation target is already visible when page loads at initial scroll offset (or perhaps its content partially outside the scroller when it is fully scroller).
Here are two ways to handle this situation:
animation starts mid-way. In this case, the concrete intersection offset is computed as "negative" value so zero scroll offset provides a non-zero time value.
adjust duration so animation plays faster. In this case the concrete intersection offset is clamped to (0, scroll max).
Seems like options 1 should be default but in future we can have extensions to Allow the clamp behavior.
Prior art and alternatives considered
Scroll timebase proposal considered an element based trigger points as an important feature.
The text was updated successfully, but these errors were encountered: