Closed khushalsagar closed 1 month ago
avoiding a fly-in that is "quite far" but the element still intersects the viewport by a single pixel
IIUC the farthest animation distance in this case would be animating from 1 edge of the viewport to the other (for the longer axis). I was assuming that is not the case authors are trying to fix in that, the element is in the viewport throughout the animation. It's the cases where the element stays offscreen for majority of the animation that looks awkward. Because then instead of getting an animation, the element just "shows up" in the last few frames of the animation. I could very likely be missing an edge case!
The other reason I like this proposal is that it makes it trivial for authors to optimize away forced rendering of offscreen content that VT currently does. But the property has to be useful in most cases for authors to actually use it. So definitely happy to get more feedback on whether this actually addresses the target use-cases!
It could be that a customizable margin property in addition would fix that
We could use insets to determine the capture area? Positive values for insets go inwards and negative ones go outwards.
Making up properties as I go, it would result in something like this:
view-transition-capture-inset: auto | <percentage-length>{1,4}; /* To be set on the transition root upon which you call startViewTransition(). For `document`, set this on `:root` */
view-transition-visibility: always | not-clipped; /* To be set on individual elements */
An alternative to view-transition-visibility
as suggested a few times here is to determine the “capture things or not” on the transition root itself. Something like:
view-transition-capture-inset: auto | <percentage-length>{1,4};
view-transition-capture-mode: all | not-clipped;
The case against view-transition-visibility
is that you need to do this on a per-element basis. OTOH is does give you fine-grained control.
The case against view-transition-capture-mode
is that it’s “all or nothing”. OTOH it’s “set once and you’re good”.
Side note: a hack would be to use scroll-driven animations to unset the view-transition-name
when an element out of view, but that’s a hack that we shouldn’t really rely on here.
We can add view-transition-capture-inset
to customize the margin for determining an element's visibility on top of view-transition-visibility
going forward.
Between view-transition-visibility
and view-transition-capture-mode
, you could do the latter with something like:
#transition-root * {
view-transition-visibility: not-clipped;
}
So seems nicer to give view-transition-visibility
which allows per element choice..?
Also, just to clarify, @noamr's argument is not about how the author expresses whether the element should be captured (based on visibility) or not. But whether the API should be about controlling whether the element is captured. Or just exposing what the element's visibility was. For example, ::view-transition-old:in-viewport
/::view-transition-old:out-of-viewport
, and let the author decide how they want to react for each case. One of which could be to override the UA default morph animation with an entry/exit animation.
My take is that the above is more complicated than it needs to be, especially given that this feature is about providing an easy declarative way to do something which is already possible in script. So the declarative solution should make ~most of the cases easy instead of trying to solve all possible cases. And also allow authors to optimize away offscreen rendering.
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 https://github.com/w3c/csswg-drafts/issues/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.
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.
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.
This is the case where both old and new elements exist for the same name:
::view-transition-old
renders the new live image instead of the old snapshot.::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:
:only-child
to specify custom entry animations for this mode.This is the case where only the old element exists for a name. A couple of options here:
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.
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.
I think that in addition to the resolution we need to enable authors ways to override/customize this behavior:
view-transition-content-overflow: auto | all | viewport
where the root defaults to viewport
and everything else defaults to `auto.::view-transition-{group|old|new}(header):old-content-hidden
except with a better name?)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.
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
orold-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
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.
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!
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.
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"?.
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?
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.
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".
Created #10581 and #10582 to account for the two issues with using view-timeline
for this:
view-timeline-inset
), perhaps with view-timeline-range
.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.
Summary of internal discussion and #10587: WebKit has raised an implementation concern regarding displaying the live image twice. We see a few options on how to resolve this:
I currently tend to like (4) as it allows optimizing the important things without totally breaking the user-experience or creating a new mental load on authors. It also allows different tiers of optimizations based on device/memory/whatever as the results are not web-observable and the tradeoff between performance and visual degradation can be played with.
WebKit has raised an implementation concern regarding displaying the live image twice.
-webkit-box-reflect
is already a thing 😄
WebKit has raised an implementation concern regarding displaying the live image twice.
-webkit-box-reflect
is already a thing 😄
True, https://github.com/WebKit/WebKit/blob/main/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.h#L654 seems to do something like cloning a layer but it's been a while since I touched CA so will leave this to @nt1m and @mattwoodrow
-webkit-box-reflect
isn't a pattern we'd like to repeat. It's intrusive and broken in many ways (e.g. video layers are not cloned for instance).
The CSS Working Group just discussed [css-view-transitions-2] Ignore offscreen elements from participating in transitions
, and agreed to the following:
RESOLVED: UA is allowed to capture old elements in low resolution if they are off-screen
Closed because the optimization use-case seems resolved for now. #10581 and #10582 are still open to track the issue of improving the ergonomics of controlling how view-transitions behave based on distance from viewport.
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 addview-transition-name
to the onscreen ones.The proposal to make this case easier is as follows:
See prior discussion on this here.