Skip to content

[scroll-animations-1] Entry/Exit Transitions for View Timeline effects #7044

Closed

Description

@fantasai

There's a strong use case for wanting to animate while an element enters or exits the view, see e.g. #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.

Activity

  1. bramus commented on Feb 16, 2022

    @bramus
    Contributor

    As copied over from #6674 (comment) (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.
      • Values are 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.
      • As an author you can opt to set two thresholds, e.g. 25% 80%.
        • Both percentages indicate "how much percent of the element should be in view".
        • The first one is the start threshold (e.g. how much percentage does the element need to be in view before the animation can start)
        • The second one is the end threshold (e.g. when the element in that much percentage in view the animation must be finished).
      • Default value is auto.
        • For enter phases that translates to 0% 100% (i.e. from element not in view and touching start edge to element entirely in view).
        • For 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:

    • The 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.
    • The 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 😅)

  2. fantasai commented on Feb 21, 2022

    @fantasai
    CollaboratorAuthor

    Notes from chatting with @bramus, @mirisuzanne, and @rachelnabors:

    • Remove view-timeline-fit
    • Split 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'> ]#
      
    • Define TimelinePhase API
        interface TimelinePhase { 
          readonly DOMString name;
          readonly double startTime;
          readonly double endTime;
        }
      
    • Give AnimationTimeline ability to say what TimelinePhases are in effect, e.g. by querying a Set of some kind.
    • ViewTimeline defines the following phases:
      • entry from when the box first comes into view from until it either is completely within view or completely covering the viewport
      • exit same, but on the other side
      • contain, while the box is fully contained within or fully covering the viewport

    Open Questions

  3. ydaniv commented on Feb 26, 2022

    @ydaniv
    Member

    These phases look pretty good for some cases, but I have couple of quite common cases which don't seem to be very straightforward using those:

    1. The "animate-while-sticky" case: was very prominent effect in the apple.com product LP's, where scrubbing the effect would start once the source becomes sticky (position), and end once it's started being pushed up again. See example: https://codepen.io/ydaniv/pen/OJOobVR. Perhaps we could achieve this with a cover phase? Or perhaps introducing margins?
    2. The "background scroll effect" case: quite common on Wix sites, where the source is a container, and the target is usually a media element clipped by the source. These events usually start in some threshold in entry and end at another on contain or even at exit. So question is how do we stretch an effect across phases?
  4. flackr commented on Mar 4, 2022

    @flackr
    Contributor
    • 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

    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 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?

    My vote would be for writing mode of the container. I don't think we should have hysteresis based on scroll direction.

  5. birtles commented on Mar 7, 2022

    @birtles
    Contributor

    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.

  6. flackr commented on Mar 17, 2022

    @flackr
    Contributor

    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.

  7. flackr commented on Apr 19, 2022

    @flackr
    Contributor

    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.

  8. kevers-google commented on Apr 29, 2022

    @kevers-google
    Contributor

    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.

  9. flackr commented on May 2, 2022

    @flackr
    Contributor

    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.

  10. bramus commented on May 2, 2022

    @bramus
    Contributor
    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

  11. kevers-google commented on May 11, 2022

    @kevers-google
    Contributor

    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%.

  12. flackr commented on May 20, 2022

    @flackr
    Contributor

    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/

  13. fantasai commented on Jun 1, 2022

    @fantasai
    CollaboratorAuthor

    @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 #7044 (comment)

    How does a developer get access to this TimelinePhase instance?

    As mentioned in #7044 (comment) : “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.

  14. 24 remaining items

  15. birtles commented on Aug 16, 2022

    @birtles
    Contributor

    @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?

  16. ydaniv commented on Aug 17, 2022

    @ydaniv
    Member

    I think cue makes less sense here since it reflects a signal/instant, not a period of time/pixels.

  17. cdoublev commented on Sep 20, 2022

    @cdoublev
    Collaborator

    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?

    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)?

  18. bramus commented on Sep 20, 2022

    @bramus
    Contributor

    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 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)?

    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.

  19. flackr commented on Oct 7, 2022

    @flackr
    Contributor

    I think this is done. As far as I can tell @fantasai you have edited in the resolutions here, right?

  20. fantasai commented on Dec 7, 2022

    @fantasai
    CollaboratorAuthor

    Yeah, looks like I forgot to close the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions