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

[css-view-transitions-2] Ignore offscreen elements from participating in transitions #8282

Open
khushalsagar opened this issue Jan 5, 2023 · 118 comments
Labels
css-view-transitions-2 View Transitions; New feature requests Needs Edits

Comments

@khushalsagar
Copy link
Member

Currently if an element has a non-none computed value for view-transition-name, it participates in the transition irrespective of whether it is in the visible viewport. This means the element will be rendered, which has significant computational and memory overhead, even if it is never seen by the user. If the developer wants to avoid this overhead, they have to keep track of the visibility of each element and only add view-transition-name to the onscreen ones.

The proposal to make this case easier is as follows:

  • Add a new CSS property view-transition-offscreen which customizes the behavior for named elements based on their position in the snapshot viewport.
  • The property has 2 values: auto and absent. auto indicates that the UA should render the element irrespective of its viewport position (as-if its onscreen). Would be nice to allow flexibility to the UA to optimize out such elements in case the transition is on memory constrained devices. That's why "should" instead of "must".
  • absent indicates that if the element's ink overflow rectangle does not intersect the snapshot viewport, the element does not participate in the transition (as-if its view-transition-name's computed value was none).
  • The computation for the element's viewport position for the decision above can be done here for old elements. And here for new elements. The subtlety is that for new elements we do it before resolving the ready promise so script can observe the final pseudo DOM structure.

See prior discussion on this here.

@khushalsagar khushalsagar added css-view-transitions-1 View Transitions; Bugs only css-view-transitions-2 View Transitions; New feature requests and removed css-view-transitions-1 View Transitions; Bugs only labels Jan 5, 2023
@jakearchibald
Copy link
Contributor

There are memory and computational benefits to this feature, but I originally proposed this as more of a visual / developer experience feature.

If two pages have a common heading, you may want that to be static in a transition, so you give it a page-transition-name.

However, if one of the pages is scrolled 8000px, the transition between the two will be bad, as the header will fly in from 8000px away.

With this feature, developers will be able to create a special incoming/outgoing animation for the header in this case (using the :only-child selector).

@jakearchibald
Copy link
Contributor

  • The property has 2 values: auto and absent. auto indicates that the UA should render the element irrespective of its viewport position (as-if its onscreen). Would be nice to allow flexibility to the UA to optimize out such elements in case the transition is on memory constrained devices. That's why "should" instead of "must".

Since it significantly changes the animation, I'm not sure we can, or should, do this automatically. Since we won't be doing it in the first release, and it will only happen on constrained devices, I think it'll lead to things appearing broken when this 'auto' behaviour kicks in. I'd rather say that transitions can be skipped if the device is constrained, since that's a more reliable fallback that developers will already be catering for.

That said, I support the default value being auto, since elements are sometimes ignored due to content-visibility #7874

@vmpstr
Copy link
Member

vmpstr commented Jan 9, 2023

It feels like this is one of these options that you should give to startViewTransition to optimize things because you know that you've marked way too much with the view-transition-name. Although I do see the appeal of the new css property since you can use that in MPA cases too, to me it doesn't really feel like the right abstraction

@jakearchibald
Copy link
Contributor

I like it as a CSS property since the instruction can be per transition item. Eg, if you're reordering a list, you still want things to transition from outside the viewport. Whereas you might not want that behaviour for a header. Both might happen in the same transition.

@khushalsagar
Copy link
Member Author

A third value which would be useful for a case like https://deploy-preview-32--infrequently.netlify.app/, the page has a massive element which will be captured in entirety while the animation doesn't need that.

Syntax ideas, view-transition-offscreen: clip allows the UA to snapshot an intersection of the element's ink overflow rectangle with the visible viewport or snapshot root. Potential add-on is view-transition-offscreen: clip inset(10px), second argument allows specifying an explicit skirt by which the snapshot should be expanded in either direction.

@jakearchibald
Copy link
Contributor

Isn't the clipping just supposed to happen automatically? https://drafts.csswg.org/css-view-transitions-1/#compute-the-interest-rectangle-algorithm

@khushalsagar
Copy link
Member Author

Isn't the clipping just supposed to happen automatically? https://drafts.csswg.org/css-view-transitions-1/#compute-the-interest-rectangle-algorithm

That automatic clipping is very conservative, only if we must because of constraints like max texture size. Technically an implementation doesn't need to be constrained by it (you could create a tiled image), but the option gives UA flexibility.

This property would be an explicit hint from the developer that only the onscreen content (or a skirt around it) of this DOM element will be animated during the transition. The UA can use this knowledge to aggressively optimize for memory by painting and snapshotting a subset of the DOM element.

@bramus
Copy link
Contributor

bramus commented Oct 5, 2023

On a hackathon I coached at this was a dealbreaker for some, as they saw performance get tanked on non-highend devices. They were animating 1 element out of a list of 50, of which only 7 of them where visible in the viewport. Having a way to easily exclude these offscreen elements would surely be beneficial here.

@jakearchibald
Copy link
Contributor

Are they happy with some items being :only-child, and animating as such, even though they existed in both states.

I wonder if we need some other feature in that case, where the group still animates from old to new, but there's only a new in the pair, or the group is dropped if the final position of the group is still out of view.

@khushalsagar
Copy link
Member Author

I wonder if we need some other feature in that case, where the group still animates from old to new, but there's only a new in the pair, or the group is dropped if the final position of the group is still out of view.

@jakearchibald we have a couple of other issues with the characteristics you mentioned:

We haven't dug into the exact API shape but given how related these 3 features are, I feel like we should tackle them together. Like a new CSS property to specify one of these modes?

@noamr
Copy link
Collaborator

noamr commented Oct 23, 2023

I like the direction this conversation was going. The main thing that justifies a new attribute for this is the idea that this could be a UX choice rather than a mere optimization (e.g. preventing a header from jumping).

I think the semantics here should be similar to content-visibility and intersection observers, however because this is observable and not just an optimization, the definitions need to be exact and customizable via a margin (same as rootMargin in IntersectionObserver).

Another thing I think we should do is make this new attribute inherited, this way the author can decide that a container makes its entire set of descendants behave in a certain way (and this can be overridden further down the tree).

Perhaps this can be view-transition-visibility or view-transition-overflow with:

  • visible: current behavior
  • auto: similar to content-visibility: auto. element acts as if it doesn't have a view-transition-name if it's not intersecting with the viewport.
  • hidden: element ignores its view-transition-name. As this attribute is inherited, this can be used to hide an entire tree from the view transitioning capture algorithm
  • clip: the element participates in the transition, but only contents of the element that intersect with the viewport are captured..

@khushalsagar
Copy link
Member Author

+1 to making the property inherited. I'm assuming the initial value will be visible.

For the auto/clip case, is there any use-case to let developers expand the snapshot viewport by some margin. So any elements/area within that is considered visible. This can also be a future extension with the current capability limited to clipping at snapshot viewport boundary.

@noamr
Copy link
Collaborator

noamr commented Oct 30, 2023

A few additional comments:

  • I wouldn't do anything regarding the ink overflow. It shouldn't be web observable... so I think the intersection should work according to the IntersectionObserver rules and disregard ink-overflow.
  • Not sure if clip should be something a different attribute. When the whole content is clipped-out, do you want to treat it as an empty image or not have it at all? e.g. perhaps still show the ink-overflow? something about clip feels a bit different as it clips the content rather than removes the whole capture.

@khushalsagar
Copy link
Member Author

I wouldn't do anything regarding the ink overflow. It shouldn't be web observable... so I think the intersection should work according to the IntersectionObserver rules and disregard ink-overflow.

Hmmm, I think there will be cases where disregarding the ink overflow would end up with glitchy behaviour. Like a widget whose shadow is in the viewport. Should we really ignore it? This has come up for IO in the past too: #8649. @szager-chromium on that.

I was curious how content-visibility handles this. Looks like it relies on overflow-clip-margin (which is developer provides) and uses that here. Even if the overflow clip edge is further than the actual ink or scroll overflow, we rely on the specified edge.

With VT we intentionally decided not to have paint containment, so that won't work for us.

Not sure if clip should be something a different attribute. When the whole content is clipped-out, do you want to treat it as an empty image or not have it at all?

Oh I just read what you said carefully and I tend to agree. We should have separate properties which decide whether the element participates in the transition. If it is participating, then a separate property has knobs for deciding how its captured. So omitting clip sounds good.

In that regard, maybe the use-case in #9354 should eventually be handled by view-transition-visibility.

@jakearchibald
Copy link
Contributor

We should have separate properties which decide whether the element participates in the transition. If it is participating, then a separate property has knobs for deciding how its captured. So omitting clip sounds good.

Absolutely agree.

@jakearchibald
Copy link
Contributor

jakearchibald commented Nov 8, 2023

I think there will be cases where disregarding the ink overflow would end up with glitchy behaviour.

Also agree. I think ink-overflow intersection with the viewport counts as visible in terms of a view transition, else you risk 'seeing double'.

@vmpstr
Copy link
Member

vmpstr commented Nov 8, 2023

I was curious how content-visibility handles this.

Yeah, content-visibility uses paint containment for this reason, so that we don't need to render the subtree to figure out the extent of the overflow, all of the needed information is on the box itself (border box + overflow-clip-margin)

@khushalsagar
Copy link
Member Author

One of the open questions for this feature is defining when an element is considered visible. There's 2 aspects to it:

  • Intersection with the viewport (snapshot root from VT perspective). Ideally we'd use the element's ink overflow rect for it. IntersectionObserver already uses ink overflow rect to detect occlusion here. And [css-overflow-3] Specify extent of ink overflow #8649 is in progress to make the ink overflow bounds interoperable.

  • Occlusion by other elements on the page. I haven't seen a use-case where a named element was occluded so I don't know if we need to consider it. Occlusion calculations are also more expensive so better if we can avoid it.

@jakearchibald
Copy link
Contributor

I'm pretty certain we don't need to care about occlusion. Just viewport intersection.

@noamr
Copy link
Collaborator

noamr commented Nov 16, 2023

One of the open questions for this feature is defining when an element is considered visible. There's 2 aspects to it:

  • Intersection with the viewport (snapshot root from VT perspective). Ideally we'd use the element's ink overflow rect for it. IntersectionObserver already uses ink overflow rect to detect occlusion here. And [css-overflow-3] Specify extent of ink overflow #8649 is in progress to make the ink overflow bounds interoperable.

  • Occlusion by other elements on the page. I haven't seen a use-case where a named element was occluded so I don't know if we need to consider it. Occlusion calculations are also more expensive so better if we can avoid it.

In either case I think we should be consistent with IntersectionObserver. For viewport intersection IntersectionObserver doesn't take the ink overflow into account, so this shouldn't either. If IO exposed ink overflow in some way, eg for occlusion, we could consider doing the same.

Note that with a big enough root margin, being accurate about the ink overflow becomes less important.

@jakearchibald
Copy link
Contributor

jakearchibald commented Nov 16, 2023

hmm, I think the developer intent of this feature trumps consistency with intersection observer here. We can avoid using the word 'intersection' if that's where the problem is.

I'll ask Shopify folks though.

@noamr
Copy link
Collaborator

noamr commented Nov 16, 2023

hmm, I think the developer intent of this feature trumps consistency with intersection observer here.

How is the developer intent here different from the developer intent in IntersectionObserver?
I think consistency is important here, having several APIs that rely on viewport-intersection and work in a slightly different manner would create confusing UX bugs and confusion. If we want to expose ink-overflow, we should do that in IntersectionObserver as well somehow.

@noamr
Copy link
Collaborator

noamr commented Nov 16, 2023

I think the underlying question here is how we would expect elements to behave that are right outside the edge of the viewport.
If this was just about performance, we could make some educated guess and tweak it. But this about excluding elements that are outside the viewport from participating.

So let's say you have an element that has a top: 100vh or some such - deliberately right outside the viewport. Should a 1px blur make it suddenly participate in the transition? Feels to me that this should not be something that relies on implementation-specific things and the author should be able to curate this with properties rather than rely on this unspeced behavior.

@khushalsagar
Copy link
Member Author

One more thing, one good point which has been raised on this issue is that if we default to ignoring offscreen elements it would break legit use-cases. And authors would just do something like * { view-transition-capture: all } to get back the old behaviour.

I suspect most cases where authors add a name to a lot of elements is list items (like changing a grid layout), where likely only the element position is changing and its content is the same. So for the perf thing, maybe #9406 is a better fix. We can default to skip caching paint for offscreen content and require authors to explicitly opt-in. Since for new live elements, we can generate paint when the pseudo comes onscreen this won't be an issue unless a cross-fade was really needed. And I don't see why that would be necessary if the old content was offscreen to begin with.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-view-transitions-2] Ignore offscreen elements from participating in transitions, and agreed to the following:

  • RESOLVED: We will design an optimization for VT of "offscreen" elements wherein we don't capture the bitmap for their offscreen state. Details TBD.
The full IRC log of that discussion <TabAtkins> khush: two problems
<TabAtkins> khush: one on my mind intiially, one brought by authors later. i'll talk about author problem first
<TabAtkins> khush: the way we decide aniamtions during an aimtion is the classic FLIP - start state, end state, set up an animation using it
<TabAtkins> khush: ways to have header/foother on the page that can be scrolled away
<TabAtkins> khush: might be super far away after scrolling
<TabAtkins> khush: simple FLIP animation gives a 1000px translation happening in 250ms which is mostly offscreen, which is effectively no animation - only on screen in last frame
<TabAtkins> khush: can work around it with intersectionobserver and conditionally put a name on it
<TabAtkins> khush: not great, needs JS
<TabAtkins> khush: so want a declarative way - rather than have a morph animation, make it entry/exit if the start/end state is offscreen
<TabAtkins> khush: my issue was - if you put a name on something, no matter how offscreen it is (content-visibility aside) then the browser will render it
<TabAtkins> khush: worse than style and layout, have to actually paint, and keep those pixels in memory
<TabAtkins> khush: costs a lot to do offscreen rednering
<TabAtkins> khush: and in the common case, offscreen things stay offscreen. then spending those resources for nothing.
<TabAtkins> khush: was hoping this could be used to let authors optimize this case for thsemelves. maybe even change the default behavior.
<TabAtkins> khush: three qs
<TabAtkins> khush: first, how to define offscreen
<TabAtkins> khush: related to the reference rect discussion, we want ink overflow, and i think that's sufficinetly specced now
<TabAtkins> khush: consistent with anchor pos too
<TabAtkins> khush: second, syntax
<TabAtkins> khush: lot of options on this issue
<TabAtkins> khush: proposing view-transition-visibility: visible | clip
<TabAtkins> khush: in the issue was suggested to match position-visibility value space, always | not-clipped
<vmpstr> q?
<bramus> q-
<vmpstr> q+
<TabAtkins> khush: we can bikeshed thenames
<TabAtkins> khush: 3, should we make clipping the default behavior?
<ydaniv> q+
<TabAtkins> khush: so easy to have offscreen content animating without realizing
<TabAtkins> khush: some author pushback, because this isn't just perf, it changes the animation
<TabAtkins> khush: like if you have a list, you delete an item, the next item is just offscreen, it just fades in rather than morphing
<TabAtkins> q+
<astearns> ack vmpstr
<TabAtkins> vmpstr: to add to pushback cases, i think the default is web-breaking, to be clear
<TabAtkins> changing the default, that is
<TabAtkins> vmpstr: some pushback on using viewport + ink-overflow
<TabAtkins> vmpstr: it's a very binary behavior, no smooth in-between
<TabAtkins> vmpstr: may be awkward to have such a different experience based on 1px scroll difference
<astearns> ack ydaniv
<TabAtkins> ydaniv: regarding name, my proposal is overflow, becuase visibility of VT sounds like your'e changing the visibility of the transition itself
<TabAtkins> ydaniv: overflow, sound slike you're talking about what you're clipping, or about elements that overflow the viewport
<TabAtkins> ydaniv: so view-transition-overflow: clip | visible
<TabAtkins> ydaniv: I think the use-cases are really common tho, think we should take care of it
<TabAtkins> ydaniv: need something to let it get clipped, don't blow up just because i have some things out of view
<TabAtkins> ydaniv: and if we add overflow-inset or margin, to increase level of tolerance
<astearns> ack TabAtkins
<astearns> q+
<fantasai> TabAtkins: I also have a minor objection to using the same definition as anchor-visibility does
<khush> q+
<fantasai> TabAtkins: I think it should be more like content-visibility where it's UA by default
<fantasai> TabAtkins: but also has a bit of margin of offscreen, so that you're ready
<fantasai> TabAtkins: it's not just offscreen, it's wayyy offscreen, so the animation is not visible at all
<fantasai> TabAtkins: So larger margin, smae concept as content-visibility
<fantasai> TabAtkins: don't think we need exact precision for it
<vmpstr> q+
<fantasai> TabAtkins: anchorpos needs it to be immediately as soon as it goes offscreen, because that's the use case
<TabAtkins> khush: i mentioned this exact thing on the issue, the dev response was with c-v the fact there's a ua-defined margin is there's a big perf thing, you don't see the difference based on the ua's choice
<vmpstr> q-
<vmpstr> +1
<TabAtkins> khush: if i'm debugging and see one behavior on chrome i won't chekc safari for a different behavior
<TabAtkins> khush: so point was to be explicit, since it changes the visual animation
<TabAtkins> khush: we can add a margin and let devs control
<fantasai> TabAtkins: viewport + margin is what I care about
<fantasai> TabAtkins: don't want super abrupt case
<fantasai> TabAtkins: We're either doing X or Y, it's abrupt
<ydaniv> q+
<astearns> ack astearns
<fantasai> TabAtkins: making it explicit and not paying cost for invisible frames just acknowledging the reality
<TabAtkins> astearns: i think an addition margin helps but doesn't entirely fix
<TabAtkins> astearns: i fyou have a list of cards, you're deleting some, and 3 or 4 move up into view - it's weird that some close to the view have a smooth transition while further ones are fading in
<TabAtkins> astearns: can do a larger margin, but still hit the case for things sufficiently far away
<astearns> ack khush
<TabAtkins> astearns: so i think i disagree we're fixing *only* the case where none of the naimation is being visible, it's also affecting where *less* of the animation is visible
<TabAtkins> (that's why we'd ahve something controllable, fwiw
<TabAtkins> )
<TabAtkins> khush: noam brought up a point - this feature is right now combining two aspects - whether the elemnt is offscreen or not, and what the browser shoudl do when it is offscreen
<TabAtkins> khush: noam's suggestion is to expose the state to the author. we dont' change wehther we capture or not, but let you know the elment was offscreen in one state (pseudo-class) so you can choose to react to that
<fantasai> TabAtkins: Doesn't solve perf problem though
<TabAtkins> khush: that's true, it's why i was pushing back
<TabAtkins> khush: i have another issue for the same perf problem
<TabAtkins> khush: like, if something is offscreen, we capture the box, just not its painted contents
<TabAtkins> khush: then we just don't cross fade, jsut use the new paint
<fantasai> TabAtkins: I quite like that
<fantasai> TabAtkins: fixes the case Alan spoke about
<fantasai> TabAtkins: and in general, seems like it's muuuch more likely to be compatible with existing content
<fantasai> TabAtkins: because geometry of animation is identical between now and new version
<fantasai> TabAtkins: only change is appearance in all but final frame, and only slightly
<fantasai> TabAtkins: so that sounds great
<TabAtkins> vmpstr: and if you're reordering lists and content isn't changing, you can just disable the crossfade and only show the new content, this solves that problem
<TabAtkins> khush: in most of these cases you really just want the transform, not the crossfade or size
<TabAtkins> khush: might want to think about size at all
<TabAtkins> khush: but if it's just moving, tkhis works perfectly well
<astearns> ack ydaniv
<TabAtkins> ydaniv: can you repeat the suggestion?
<TabAtkins> khush: [repeats it]
<TabAtkins> khush: and i think that's ok becuase you want to cross-fade when both old and new is visible to the user. if one state wasn't visible, no need to crossfade
<TabAtkins> ydaniv: yeah. and to alan's point, right now everything is captured, so you'll see all the cards.
<TabAtkins> ydaniv: I think the suggestion is that you'll clip.
<astearns> ack fantasai
<TabAtkins> [not 100% sure i captured ydaniv properly there]
<TabAtkins> fantasai: +1 to this idea too
<TabAtkins> fantasai: and for the pseudo-element indicating what's happening
<TabAtkins> fantasai: pseudo-class
<TabAtkins> fantasai: i think you need to expose all three cases - off-screen start, end, or both
<ydaniv> s/I think the suggestion is that you'll clip./the suggested property is an opt-in so you're asking to clip it for you when you want to optimize/
<TabAtkins> fantasai: maybe :offscreen and :offscreen(start | end | both)
<fantasai> s/start | end/new | old/
<TabAtkins> flackr: case to consider - new is above the screen, old is below the screen, so the animation passes *thru* the screen
<TabAtkins> fantasai: yes, interesting, should think about it
<TabAtkins> fantasai: might want to not transition at all
<TabAtkins> bramus: or might want to explicitly see that - if you're reordering a list and the part on screen stays still, want an indicator that the rest is flying around
<TabAtkins> fantasai: so want to consider the conditional styling there
<TabAtkins> khush: guess in that case you jsut want the current behavior
<TabAtkins> khush: whatever animation the browser sets up today
<TabAtkins> flackr: I think you'd be able to skip the capture, but still animate the new image
<fantasai> TabAtkins: Falls into same problem as you might see only 1 frame of the animation
<fantasai> TabAtkins: I think we can probably use the new state render for that and call it a day
<TabAtkins> astearns: so should we take all this discussion to the issue and synthesize a new proposal?
<TabAtkins> fantasai: seem to have agreement to pursue keeping the element, nto capturing the bitmap
<flackr> +1
<ydaniv> +1
<TabAtkins> fantasai: and maybe a pseudoclass for doing something special when something is offscreen
<fantasai> astearns: by how much offscreen?
<fantasai> TabAtkins: I think we can let the UA define
<fantasai> flackr: could be somewhat similar to content-visibility
<fantasai> vmpstr: new property or change behavior?
<fantasai> TabAtkins: I think you could just change the behavior.
<fantasai> astearns: How about we resolve that we want to add an optimization for offscreen and continue
<TabAtkins> astearns: let's jsut resolve that we want to add an optimization for VTs of offscreen elements. continue the discussion in the issue.
<TabAtkins> fantasai: I think we are specific about the bitmap dropping
<TabAtkins> astearns: proposed: we *will* add an optimization for offscreen things where we wont' retain the bitmap
<TabAtkins> RESOLVED: We will design an optimization for VT of "offscreen" elements wherein we don't capture the bitmap for their offscreen state. Details TBD.

@khushalsagar
Copy link
Member Author

Offline discussion on this today. We have a proposal for how the pseudo-element rendering changes if the old element is offscreen at capture time.

Morph Animation

This is the case where both old and new elements exist for the same name:

  • The geometry animation on the group pseudo is same as before.
  • ::view-transition-old renders the new live image instead of the old snapshot.
  • Instead of setting up cross-fade animations, ::view-transition-old has a 0->0 opacity animation and ::view-transition-new has a 1->1 opacity animation. This is mostly to preserve existing behaviour where we use animation-fill-mode: both to set the final value for these pseudos.
  • -ua-mix-blend-mode-plus-lighter is not added to the animation on old/new pseudos since we're not cross-fading them.

A few other options considered were:

  • Don't generate the old pseudo at all. This will conflict with author expectations that this situation is limited to entry animations. Authors even use :only-child to specify custom entry animations for this mode.
  • Generate the old pseudo but don't have any image there. The concern with this is that it might be a larger compat issue if authors have custom animations (like slide out the old and slide in the new). From a perf perspective, since the default UA animations is rendering old at 0 opacity we can also optimize away the rendering.

Exit animation

This is the case where only the old element exists for a name. A couple of options here:

  1. Generate the old pseudo but render no content in it. And switch from a fade out animation to a 0->0 opacity animation.
  2. Don't generate any pseudos for this name at all, since we'll basically be doing animations with empty boxes.

Leaning towards 1) because again least compat risk.


The question for how we define an element is offscreen is still open and can be discussed separately.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-view-transitions-2] Ignore offscreen elements from participating in transitions, and agreed to the following:

  • RESOLVED: ::view-transition-old` renders new live image (if available) if old element is offscreen at capture time
  • RESOLVED: The geometry animation on the `::view-transition-group` pseudo is same as before.
The full IRC log of that discussion <fantasai> s/any other/Seeing strong consensus here, can always re-open. Any other/
<chrishtr> khush: this is a continuation of something discussed at the F2F
<chrishtr> khush: we had a high-level resolution that we should avoid capturing things offscreen, and in particular not capture images for them
<chrishtr> khush: today I'd like to go into UA styles for this
<chrishtr> khush: for something in the old and new state, proposal is that we keep size animations
<fantasai> +1
<chrishtr> khush: another question was whether to keep the pseudo element for the old thing. propose to keep it because there developer code expects it
<chrishtr> khush: "there is developer code that expects it"
<chrishtr> khush: think there would be less of a compat risk if the new image had something instead of nothing?
<chrishtr> khush: by default you get a cross-fade in these cases, but don't want to do that and instead just show the new image
<chrishtr> khush: propose that there be a 0->0 opacity animation for the old and 1->1 for the new. reiterating that all this is to minimize compat risk.
<chrishtr> khush: also propose removing mix-blend-mode: plus-lighther
<chrishtr> khush: for exit animations, seems less clear
<chrishtr> khush: in these case you're animating an element that has no content
<chrishtr> khush: one option is to generate a new pseudo and animations are technically there but irrelevant but it doesn't exist, another option is to have no pseudo
<Rossen7> ack fantasai
<chrishtr> fantasai: I agree that the option about not generating a pseudo is a bad option, we shouldn't do that
<chrishtr> fantasai: as for generating the pseudo but having no image backing it, the whole point is for cases where it's offscreen before and after right?
<chrishtr> khush: propose we do it based only on the old state, because it has to be done at the beginning of the animation.
<chrishtr> khush: even for cases where things animate on screen it seems ok to skip the image because the user never saw the old content anyway
<chrishtr> khush: reason to keep around the pseudo is because the developer might have animation keyframes for it
<chrishtr> s/khush/flackr/
<chrishtr> fantasai: the alternative would be that the old pseudo has no image and the new one is 1->1. Either way you're just showing the new image
<chrishtr> flackr: default behavior is the same in either case
<chrishtr> khush: that's why I'm lukewarm on it, because it's hard to say what is less compat risky without deeper analysis
<chrishtr> fantasai: would it be a problem if we set up the cross-fade animation as normal?
<chrishtr> khush: if you set up the cross-fade as normal, then you're assuming the pseudos are all set up and it cross-fades?
<chrishtr> khush: yes, but it makes it harder to optimize out in the browser implementationo
<chrishtr> fantasai: if it's really needed for optimization that's fine, but otherwise just due to implementation complexity..
<chrishtr> vmpstr: we can probably optimize the animation by detecting this situation
<chrishtr> khush: if we keep the default cross-fade it is harder to detect...
<chrishtr> vmpstr: harder optimization but plausible to do so
<chrishtr> khush: if needed I can find a hack to optimize
<chrishtr> flackr: from a developer perspective it's better not to have animations be different in these situations
<chrishtr> khush: remaining questions is just about not having cross-fade, and ?
<chrishtr> khush: ok resolving on the first two question and then investigating implementability of avoiding cross-fade changes
<flackr> +1
<khush> `::view-transition-old` renders new live image (if available) if old element is offscreen at capture time
<chrishtr> RESOLVED: ::view-transition-old` renders new live image (if available) if old element is offscreen at capture time
<khush> The geometry animation on the `::view-transition-group` pseudo is same as before.
<chrishtr> RESOLVED: The geometry animation on the `::view-transition-group` pseudo is same as before.

@noamr
Copy link
Collaborator

noamr commented Jul 10, 2024

I think that in addition to the resolution we need to enable authors ways to override/customize this behavior:

  • override the default, stating that a particular element should not have its contents clipped. This could perhaps also apply to the root element, except with a different user-agent default, e.g. view-transition-content-overflow: auto | all | viewport where the root defaults to viewport and everything else defaults to `auto.
  • Customize the default animation behavior when we're displaying the new live snapshot instead of the old snapshot. Thinking something like a pseudo-class on the pseudo elements (::view-transition-{group|old|new}(header):old-content-hidden except with a better name?)

@khushalsagar
Copy link
Member Author

override the default, stating that a particular element should not have its contents clipped.

Did you mean to say, "a particular element should have its contents clipped". Because the default for everything except the root is no ancestor/viewport clipping. Agreed that we should have something like this but we can treat it as a separate problem? Just want to confirm that there's no reason a property like this should be done along with the behaviour change we resolved on.

Customize the default animation behavior when we're displaying the new live snapshot instead of the old snapshot.

This makes sense, authors should be able to detect this state. How about old-content-skipped or old-content-captured? The name should make it clear that old content was available (this isn't an entry animation) but we explicitly didn't capture it.

We'd likely eventually add a property to opt-in to this behaviour even for cases where the old element is onscreen. All the grid re-layout type of transitions where elements move onscreen without changing content can be optimized then. So the name shouldn't make it sound like the old content was offscreen. This is where I wasn't sure if old-content-hidden is ambiguous.

@noamr
Copy link
Collaborator

noamr commented Jul 10, 2024

override the default, stating that a particular element should not have its contents clipped.

Did you mean to say, "a particular element should have its contents clipped". Because the default for everything except the root is no ancestor/viewport clipping. Agreed that we should have something like this but we can treat it as a separate problem? Just want to confirm that there's no reason a property like this should be done along with the behaviour change we resolved on.

No, sorry, I mean "a particular element should not have its content clipped, even if it is away from the viewport".

Customize the default animation behavior when we're displaying the new live snapshot instead of the old snapshot.

This makes sense, authors should be able to detect this state. How about old-content-skipped or old-content-captured? The name should make it clear that old content was available (this isn't an entry animation) but we explicitly didn't capture it.

Sounds good!

We'd likely eventually add a property to opt-in to this behaviour even for cases where the old element is onscreen. All the grid re-layout type of transitions where elements move onscreen without changing content can be optimized then. So the name shouldn't make it sound like the old content was offscreen. This is where I wasn't sure if old-content-hidden is ambiguous.

+1

@khushalsagar
Copy link
Member Author

No, sorry, I mean "a particular element should not have its content clipped, even if it is away from the viewport".

Ah got it. More like, "a particular element's content should be captured (instead of using the new content) even if its away from the viewport".

In that case I meant the same property as you in "We'd likely eventually add a property to opt-in to this behaviour even for cases where the old element is onscreen". A tri-state CSS property to decide capturing of old content: always capture, never capture and only capture when onscreen (which is the default). Can bikeshed on naming.

@noamr
Copy link
Collaborator

noamr commented Jul 12, 2024

As far as the OP goes, this is achievable today using scroll-driven animations. See this demo: https://codepen.io/noamr-the-selector/pen/jOjPBXP

Basically the element in question has a view-timeline-name and an animation-timeline referencing itself, with @keyframes that animate the view-transition-name property. This makes it so that only when the element is in the viewport it has a view-transition-name. The offset can be controlled using view-timeline-inset.

It's perhaps hacky to use SDA for this but it works today and can be used for other things than view-transitions as well!

@jakearchibald
Copy link
Contributor

It's perhaps hacky to use SDA for this

It is. I'm sad that the OP has been ignored given the number of developers asking for this. I guess we can continue to solve it with JavaScript.

@noamr
Copy link
Collaborator

noamr commented Jul 15, 2024

It's perhaps hacky to use SDA for this

It is. I'm sad that the OP has been ignored given the number of developers asking for this. I guess we can continue to solve it with JavaScript.

Given the following:

@keyframes apply-view-transition-name-when-in-range {
  from { view-transition-name: var(--view-transition-name);  }
  to { view-transition-name: var(--view-transition-name); }
}

header {
  --view-transition-name: header;
  animation-name: apply-view-transition-name-when-in-range;
  animation-timeline: view();
}

Can you be more specific about what behavior the OP requests that's different? Is it just the aesthetic aspect of using an animation? If It looked something like this (with the exact same behavior) would it make any difference?

header {
  @in-view {
     view-transition-name: header;
  }
}

Note that using view timelines for this has the added value of being able to control a different behavior when slightly off screen and far away from screen:

@keyframes apply-view-transition-name{
  from { 
   view-transition-name: var(--view-transition-name);  
   view-transition-class: near;
  }
  10% { view-transition-class:  inside; }
  90% { view-transition-class:  inside; }
  to { 
    view-transition-class: near;
    view-transition-name: var(--view-transition-name); 
  }
}

header {
  --view-transition-name: header;
  animation-name: apply-view-transition-name;
  view-timeline-inset: 50vmax;
  animation-timeline: view();
}

I think the problem with this issue is that it defines a scroll-driven behavior for view-transitions and we are trying to do this as a shortcut, while we already have a scroll-driven primitive that can do this, and the main issue with it is that it's tied to "animations"?.

@jakearchibald
Copy link
Contributor

Is it just the aesthetic aspect of using an animation?

Pretty much, yeah. I'm guessing it doesn't account for ink overflow either? Are Chrome's dev rels happy with documenting this feature as a hack with scroll timelines?

@noamr
Copy link
Collaborator

noamr commented Jul 15, 2024

Is it just the aesthetic aspect of using an animation?

Pretty much, yeah. I'm guessing it doesn't account for ink overflow either?

Correct, you have to set view-timeline-inset explicitly. But this is a known view-timeline issue, e.g. if you currently use text as your view timeline it wouldn't account for the overflowing edges.

Are Chrome's dev rels happy with documenting this feature as a hack with scroll timelines?

We'll discuss this internally to see if people are content with it. I have it on my list to document it (and another hack to override the "don't capture contents if it's far from the screen" behavior we resolved on here) as ways to change view-transition behavior based on scroll position of participating elements.

@khushalsagar
Copy link
Member Author

I'm sad that the OP has been ignored given the number of developers asking for this.

@jakearchibald apologies if it came across that this feature request is being ignored. OP's use-case of customizing the UX when content is offscreen was explicitly discussed at the last CSS f2f, the detailed notes are here.

One of the questions raised in that discussion was if we should be providing the offscreen state of the old/new DOM elements as a pseudo-class. Something like :offscreen and :offscreen(start | end | both). Then authors can choose to react to these states by specifying a custom animation. That seems more flexible than a property which says, "ignore this element's name if its offscreen".

@noamr
Copy link
Collaborator

noamr commented Jul 16, 2024

Created #10581 and #10582 to account for the two issues with using view-timeline for this:

  • Account for ink-overflow somehow (beyond the explicit view-timeline-inset), perhaps with view-timeline-range.
  • using animation for something that's not really an animation (but just a range) is weird

I believe that these issues belong in scroll-driven animations and not in view transitions, as they define scroll-related behavior. For example, the same patterns can be used to change the behavior of a regular CSS transition if it's away from the viewport.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
css-view-transitions-2 View Transitions; New feature requests Needs Edits
Projects
Status: Thursday afternoon
Feb 2024 Agenda
unsorted FTF
Development

No branches or pull requests