Closed fantasai closed 1 year ago
As copied over from https://github.com/w3c/csswg-drafts/issues/6674#issuecomment-930542414 (per request by @fantasai):
Note that these "element slides into view"-animations can also be about a portion of that element, e.g. "only start when a quarter into view" or "be finished when 75% in view". I've done so in https://www.bram.us/2021/03/04/the-future-of-css-scroll-linked-animations-part-2/#demos--revealing-images for example where the animation runs from 50% into view to 100% into view.
Above that several animations can be attached to several phases of a timeline. See https://www.bram.us/2021/03/04/the-future-of-css-scroll-linked-animations-part-2/#demos--contact-list-revisited for example, where there are different enter and exit animations on the same element.
π‘ To implement this it looks like a good idea to me to have one shared view-timeline
for an element, and then be able to tap into parts of that view-timeline
(e.g. the enter and exit phases) to hook animations on.
I'm thinking of two new properties here:
animation-timeline-phase
to define which part of the view-timeline
you want attach your animation on.
enter
, exit
and auto
(= for the entirety of the view-timeline
, with respect to view-timeline-fit
)animation-timeline-thresholds
to define the element-based thresholds for running the animations.
25% 80%
.auto
.enter
phases that translates to 0% 100%
(i.e. from element not in view and touching start edge to element entirely in view).exit
phases that translates to 100% 0%
(i.e. from element entirely in view and touch end edge to element entirely out of view).The adjusted syntax for the https://www.bram.us/2021/03/04/the-future-of-css-scroll-linked-animations-part-2/#demos--contact-list-revisited example would then become:
@keyframes animate-in {
β¦
}
@keyframes animate-out {
β¦
}
ul {
overflow-y: scroll;
}
li {
view-timeline: li-in-ul;
view-timeline-fit: cover;
}
li > * {
animation:
animate-in 1s linear forwards,
animate-out 1s linear forwards;
animation-timeline:
li-in-ul,
li-in-ul;
animation-timeline-phase:
enter,
exit;
animation-timeline-thresholds:
25% 100%,
100% 25%;
}
In this example:
animate-in
animation would run during the enter
phase of the li-in-ul
timeline. The animation will start when the watch element (the li
) is 25% in view at the start edge and be finished when the watched element is entirely in view.animate-out
animation would run during the exit
phase of the li-in-ul
timeline. The animation will start when the watch element (the li
) is 100% in view at the exit edge (e.g. about to leave) and be finished when the watched element is 75% out of view.π€ Braintwist: If these new properties would exist, is view-timeline-fit: cover;
still feasible as we're basically delegating that responsibility to animation-timeline-phase
? animation-timeline-phase
could in that case be extended to accept the values cover
and contain
, deprecating view-timeline-fit
.
π€ Braintwist on the braintwist: Sayanimation-timeline-phase
would be extended as suggested in the braintwist above and a user wants to do something like "run the animation from the moment the element is entering the scrollport until it is halfway in the scrollport", then the enter
/exit
/cover
/contain
keywords won't do.
In that case I'm sliding back to a syntax using an edge and a percentage, which is awfully similar to <element-offset>
from the current proposal:
animation-timeline: some-element-in-a-scroll-container;
animation-timeline-phase-start: end 0%; /* Start animation when the watched element is sitting at the end edge, with 0% in view*/
animation-timeline-phase-end: 50%; /* Have animation be finished by the time the watched element is in the middle of the scroll-container*/
To combine these in the animation-timeline-phase
shorthand a separator other than ,
would be needed.
Thinking of /
right now (e.g. animation-timeline-phase: end 0% / 50%;
) as using a space (e.g. animation-timeline-phase: end 0% 50%;
) makes it somewhat weird: the first percentage is a threshold but the second percentage is a progression of the view-timeline
.
(Apologies for freewheeling here. My mind is playing ping-pong with itself when it comes to Scroll-Linked Animations π )
Notes from chatting with @bramus, @mirisuzanne, and @rachelnabors:
view-timeline-fit
animation-timeline
into animation-timeline-name
and animation-timeline-phase
, with animation-timeline
shorthand
animation-timeline-name: [ auto | none | <timeline-name> ]#
animation-timeline-phase: [ active | <timeline-phase-name> ]#
animation-timeline: [ <'animation-timeline-name'> <'animation-timeline-phase'> ]#
interface TimelinePhase {
readonly DOMString name;
readonly double startTime;
readonly double endTime;
}
entry
from when the box first comes into view from until it either is completely within view or completely covering the viewportexit
same, but on the other sidecontain
, while the box is fully contained within or fully covering the viewportOpen Questions
entry
and exit
distinguished? Is it by writing mode of the scroll container, or are we tracking where the box originally came into view from somehow?These phase
s look pretty good for some cases, but I have couple of quite common cases which don't seem to be very straightforward using those:
cover
phase? Or perhaps introducing margin
s?entry
and end at another on contain
or even at exit
. So question is how do we stretch an effect across phases?
- Define TimelinePhase API
interface TimelinePhase { readonly DOMString name; readonly double startTime; readonly double endTime; }
How does a developer get access to this TimelinePhase instance? In the CSS API the phase is named, wouldn't they name the phase in the JS api as well? E.g. the same as naming fill: 'both' | 'forwards' | 'backwards' | 'none'?
Minor nit: These times should be CSSNumberish to match the units used by the timeline based on how we resolve issue #7045, whether that's pixels or percentages.
ViewTimeline defines the following phases:
Do we need a phase such as cover
which would be from entry to exit or would this be the active
phase?
Open Questions
- Are these called phases, or something else, given we have these phases already: https://www.w3.org/TR/web-animations-1/#the-timelinephase-enumeration
We may be able to remove the existing timeline phase (Timeline phase was added to be able to disambiguate time 0 when the scroller actually reaches the 0 position from before reaching that position - which could be more easily handled by allowing times outside of the timeline's "active" range, see #4325) , or simply augment the timeline phase with these new phases.
- How are
entry
andexit
distinguished? Is it by writing mode of the scroll container, or are we tracking where the box originally came into view from somehow?
My vote would be for writing mode of the container. I don't think we should have hysteresis based on scroll direction.
Jumping on the bikeshedding part here: I'd vote for not using the term "phase" here when describing the relationship of the animation to element positioning/containment. Phases are already used in a few places like effect phases and the timeline phases mentioned above. In both cases they have values "before", "active", "after" (and "inactive") and are a read-only value describing something about the progress of the animation.
This feels like quite a different use of the term such that overloading it would cause confusion.
I should add I don't think this solves the original use case as simply as offsets did. e.g. with offsets you can apply an entry and exit effect with a single animation:
const timeline = new ScrollTimeline({
scrollOffsets: [
{ target: image, edge: 'start', threshold: 0 },
{ target: image, edge: 'start', threshold: 100 },
{ target: image, edge: 'end', threshold: 100 },
{ target: image, edge: 'end', threshold: 0 }
]
});
const effect = new KeyframeEffect(
image,
{ opacity: [0, 1, 1, 0]},
{ duration: 1000, fill: both }
);
const revealUnrevealAnimation = new Animation(effect, timeline);
revealUnrevealAnimation.play();
However, defined as phases this has the exact issue that was of concern in #4912, that the entry and exit need to be separate animations. That said, I think it's workable to define these separately as I find it a little awkward that if you don't match the number of scroll offsets to the number of keyframes that the offsets are not keyframe aligned.
I think a simple solution is supporting entry/exit animations by adding start
and end
values (logical edges of the scroller) to view-timeline-fit
and possibly renaming view-timeline-fit
to something more appropriate given it's not quite the same as other fit properties, perhaps view-timeline-range
? This could optionally be a two-value attribute to allow specifying a different point for the entry from the exit.
Could you explain more about how supporting specified phases on an animation works? If the object is larger than the scroll port, the phases can overlap. What time do you get from the animation when two phases are active? Does the exit phase automatically go from 100% to 0% progress to reverse the enter phase? I think the phases should always be a single start / end offset range - so we could support specifying a start and end phase / fit and that would set the 0% and 100% progress points respectively on that scrolling axis.
view-timeline-range: cover | contain | start | end | [ custom offset pair ]
sounds good. I share Brian's concern over potential confusion if we overload 'phase" with a different meaning from effect phase and timeline phase.
We can create some interesting effects with just a single start and end offset by using additional keyframes in the animation, animation delays, and timing functions.
If the ease of use gap is deemed too great between the keyword values for view-timeline-range and the custom offsets, we could add a bit more flexibility by introducing view-timeline-insets. This would allow you to use a named range as a starting point and tweak the start and end offsets (e.g. contain but with a 20px margin). This approach may be easier than computing custom offsets, keyframe boundaries or animation delays for some users.
I think the main challenge is that the current definition of cover
and contain
implicitly correlate start / end offset to opposite edges of the viewport. If we could specify which edge of the viewport we were referring to, enter and exit animations are equivalent to the ranges [end 0%, end 100%]
and [start 100%, start 0%]
(note end is first because the item first comes in to the "end" side of the viewport). What if we allowed specifying both offsets in terms of the start and end edges of the viewport, e.g.
view-timeline-range: [start|end]? <percentage> [start|end]? <percentage>?
If you omit either [start|end]
value it would imply the end
edge followed by the start
edge. If you omit <percentage>
we use the same value.
In this syntax, cover would map to end 0% start 0%
, contain would map to end 100% start 100%
, enter would be equivalent to end 0% end 100%
and exit would be equivalent to start 100% start 0%
. If we assume the existing mapping of cover to 0% and contain to 100% then the existing view-timeline-fit values would work as expected.
Note that specifying the start
edge followed by the end
edge would almost always produce a backwards range. This is a pre-existing problem with contain
when the object is larger than the viewport.
view-timeline-range: [start|end]? <percentage> [start|end]? <percentage>?
I like this, as it gives me β as an author β the freedom to use any combination without needing to speed up keyframes or anything like that! (Also see what I described earlier in "Braintwist on the braintwist")
This is a pre-existing problem with
contain
when the object is larger than the viewport.
Sometimes it also is an opportunity, as demonstrated in this demo: https://codepen.io/bramus/pen/QWGbOBQ
I also quite like:
view-timeline-range: [start|end]? <percentage> [start|end]? <percentage>?
This would even allow reversing the direction of the animation by reversing the edges.
progress = (position - start) / (end - start) * 100%
also works if start > end as both the numerator and denominator will be negative when the scroll position is between the end points, resulting in a progress that is bounded between 0 and 100%.
FYI the scroll timeline polyfill has an implementation of ViewTimeline with the four values, but not yet the additional proportions. You can see how these work on the demo page: https://flackr.github.io/scroll-timeline/demo/view-timeline/
@birtles Wrt phases... I think it is an overlapping concept with timeline phases, it's just that we'd need timeline phases to be non-exclusive. You'd be in the active phase as well as one or more named phases specific to that timeline. Don't think it's particularly inconsistent to call those phases.
@flackr
wrt https://github.com/w3c/csswg-drafts/issues/7044#issuecomment-1059339349
How does a developer get access to this TimelinePhase instance?
As mentioned in https://github.com/w3c/csswg-drafts/issues/7044#issuecomment-1047134956 : βGive AnimationTimeline ability to say what TimelinePhases are in effect, e.g. by querying a Set of some kind.β ... we didn't hash out exactly what that API would look like. Seems from your comment that maybe we can repurpose .phase
?
Do we need a phase such as cover which would be from entry to exit or would this be the active phase?
Coincides with active, but I suppose an alias would be possible since we'd already need to support overlapping phases.
My vote would be for writing mode of the container. I don't think we should have hysteresis based on scroll direction.
WFM.
@flackr @kevers-google
view-timeline-range: cover | contain | start | end | [ custom offset pair ] view-timeline-range: [start|end]?
[start|end]? ?
I like this direction for replacing view-timeline-fit
.
I'll note doesn't solve the problem mentioned earlier of binding entry and exit animations to the entry and exit phases in one animation, though. For an animation to do that, it needs to assign frames specifically to the start/end of the entry and exit phases. We could allow the creation of multiple view timelines on a single element and assign them separate animations, but I think it's more natural to have a single timeline and an ability to bind frames to named phases or bookmarks in that timeline. And to do this in a way that the common things to do (like entry + exit animations) don't need a lot of manual setting up.
The CSS Working Group just discussed [scroll-animations-1] Entry/Exit Transitions for View Timeline effects
.
I think naming phases (specific term to be bikeshedded) from the timeline to use should work fine as long as they're not disjoint. However, given that setting a phase is equivalent to implying the startTime (which currently I think can only be explicitly set from the web animations API) and duration, we could consider exposing this explicitly as setting the start time and end time of an animation in reference to phases on its timeline. E.g.
animation-start-time: <timeline-phase-name> <percent>
animation-end-time: <timeline-phase-name> <percent>
Longer term we could even accept these as keyframe offsets similar to how from
and to
map to 0% and 100% we could map <timeline-phase-name> <percent>
to the appropriate offset of that point within the animation's overall duration.
e.g.
@keyframes slide-in-out {
from { transform: translateY(100%); }
contain 0% { transform: none; }
contain 100% { transform: none; }
to { transform: translateY(-100%); }
}
Of course open to bikeshedding the exact syntax.
Expanding on Rob's idea:
In the web-animations-1 spec, timeline.currentTime is a scalar quantity and used in animation.currentTime calculations. It is possible that we could extend the notion of current time (in web-animations-2), adding a context-specific time, where the context would be the phase-name in the case of a view timeline. A monotonic timeline or a ScrollTimeline that is not a ViewTimeline would have a single global context. The value of timeline.currentTime would be based on the default/global context for the timeline; however, calculations to get/set the start/current time of an animation could be updated to use the context specific time, defaulting to the global context if the context has not been set.
This would allow multiple animations to use the same ViewTimeline instance with different ranges/phases.
@kevers-google I think we're saying different things. In my simplified proposal, currentTime would be the same, and what would change is at which times your animation starts. For example: ViewTimeline
Specifying contain 0%
would be internally translated into X%
Gotcha. So an animation would run between X% and Y% where the percentages are calculated based on the named range?
We already have the start and end delays for an animation. Would it make sense to extend their use to cover use cases for a ViewTimeline? Alternatively, the start and end times could be used to set the delays.
@birtles Wrt phases... I think it is an overlapping concept with timeline phases, it's just that we'd need timeline phases to be non-exclusive. You'd be in the active phase as well as one or more named phases specific to that timeline. Don't think it's particularly inconsistent to call those phases.
I'm still not stoked about overloading the term "phase" here -- it seems like a different layering, at least if it's something mutable. It seems like "cue" or something like that would be better for the mutable property? For describing the current state of the world like in Rob's example (e.g. "start of the enter phase") that phase makes sense, however.
Longer term we could even accept these as keyframe offsets similar to how
from
andto
map to 0% and 100% we could map<timeline-phase-name> <percent>
to the appropriate offset of that point within the animation's overall duration.e.g.
@keyframes slide-in-out { from { transform: translateY(100%); } contain 0% { transform: none; } contain 100% { transform: none; } to { transform: translateY(-100%); } }
Of course open to bikeshedding the exact syntax.
Recently came up with something like this, which seems pretty similar:
@keyframes reveal-somethingelse-unreveal {
enter {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
contain {
0% {
β¦
}
}
exit {
to {
opacity: 0;
}
}
/* Regular keyframes, in case not linked to a view-timeline */
from {
opacity: 1;
}
to {
opacity: 0;
}
}
To my surprise this syntax doesn't seem to break current implementations, which is nice :)
@bramus In your example, the regular keyframe from and to would overwrite the enter 0%
and exit to
values. In this particular case they're the same so it wouldn't matter but just thought I'd point out that the presumption is that regular percentage keyframes would still be included.
Then in order to ensure it still doesn't break after we support these phase blocks I think phase keyframes which are not part of the attached timeline would be ignored.
@tabatkins @fantasai Do you have the notes from the meeting?
π throwing another naming idea for phase
: view-phase
π
Summary of where I think we're at from the meeting notes:
phase
API from Web Animations 1, and remove the before/after concepts, keeping just active/inactive states.@keyframes { ... exit 0% { ... } ... }
See flackr's commentanimation-delay: contain 0%; animation-duration: contain 100%
attaches the animation between the 0% and 100% points in the contain phase. (See also issue 4342].)animation-phase: contain
expands to animation-delay: contain 0%; animation-end-delay: contain 100%
.AnimationTimeline.getTime()
which returns .currentTime
by default, and returns time within the named phase if one is passed in.Extra notes (mainly for myself so that I donβt forget π ):
fade-in
from a generic library to be used as the keyframes for the enter
phasefade-in
only between enter 50% 100%
(was also included in list of comments above)The CSS Working Group just discussed entry/exit transitions for view timeline effects
, and agreed to the following:
RESOLVED: Accept all of fantasai's bullet points from her summary, except for the keyword+% in @keyframes
The CSS Working Group just discussed breakout summary
, and agreed to the following:
RESOLVED: Adopt the previously-skipped bullet point from fantasai's summary
- Remove the
phase
API from Web Animations 1, and remove the before/after concepts, keeping just active/inactive states.
FYI, removing the phase API and before/after from web-animations-1 was already resolved in #7240
- Add shorthand that expands a phase name to 0% start delay and 100% end delay. E.g.
animation-phase: contain
expands toanimation-delay: contain 0%; animation-end-delay: contain 100%
.
@birtles I wanted to get your take on the phase naming here. I agree that the term phase is overloading a concept that we already have in animations (even if it's currently not really a factor in timelines with them being either active or inactive) with something that isn't the same.
How about a vote on possible alternatives with emoji reactions? Feel free to comment and suggest alternatives: π animation-phase: enter; π animation-range: enter; π animation-time-range: enter; β€ animation-cue: enter;
@birtles I wanted to get your take on the phase naming here. I agree that the term phase is overloading a concept that we already have in animations (even if it's currently not really a factor in timelines with them being either active or inactive) with something that isn't the same.
How about a vote on possible alternatives with emoji reactions? Feel free to comment and suggest alternatives: π animation-phase: enter; π animation-range: enter; π animation-time-range: enter;
Thanks! One other thought, does animation-cue
make sense?
I think cue
makes less sense here since it reflects a signal/instant, not a period of time/pixels.
Regarding the latest changes from https://github.com/w3c/csswg-drafts/commit/2152a232f0b050e2aa148d95f32a0fff61cf259b, can you please provide me some clarifications on the grammar of <timeline-range-name>
? Can it be defined with a basic syntax like <custom-ident>
(to allow authors to declare their own custom named timeline ranges in a future API), or with |
separated timeline range names, currently restricted to the view progress named timeline ranges: cover | contain | entry | exit
?
Also, both animation-range
and animation-delay
are shorthands for animation-delay-start
and animation-delay-end
? What is the purpose of animation-range
? Which shorthand should serialize (assuming both sub-properties have a declaration)?
Regarding the latest changes from 2152a23, can you please provide me some clarifications on the grammar of
<timeline-range-name>
? Can it be defined with a basic syntax like<custom-ident>
(to allow authors to declare their own custom named timeline ranges in a future API), or with|
separated timeline range names, currently restricted to the view progress named timeline ranges:cover | contain | entry | exit
?
Right now only those 4 β cover | contain | entry | exit
β are defined/allowed. There currently is no intention to allow authors to define their own view timeline ranges β at least not at this point.
I think the rather loose syntax is there because that particular section will move to the css-animations-2/web-animations-2 spec over time. This leaves it open for other specs β other than scroll-animations-1 β to also define extra possible values, without requiring an update to the (future) css-animations-2/web-animations-2 specs.
Also, both
animation-range
andanimation-delay
are shorthands foranimation-delay-start
andanimation-delay-end
? What is the purpose ofanimation-range
? Which shorthand should serialize (assuming both sub-properties have a declaration)?
Same question. Found it weird to have two properties target the same> Otoh I do understand the need for animation-range
: it is more readable in the content of scroll-linked animations. E.g.animation-range: exit
is easier to grasp than animation-delay: exit
.
I think this is done. As far as I can tell @fantasai you have edited in the resolutions here, right?
Yeah, looks like I forgot to close the issue.
There's a strong use case for wanting to animate while an element enters or exits the view, see e.g. https://github.com/w3c/csswg-drafts/issues/4912
However View Timelines as currently defined don't have the ability to bookmark the start and end of the entry or exit phase and to attach a set of animation frames to just that phase.
We could change the ability of view-timeline-fit to match the entry or exit phase, rather than the full cover or contain phase; but this would mean that many common use cases become a patchwork of separate animations on separate timelines stitched together, which is awkward.