Closed chrishtr closed 1 year ago
I thought the concept of "UA-private except for transitions" seemed odd at first, but I suppose it could be expressed as adding a UA rule (before the other rules):
* {
top-layer: initial !important;
}
I thought the concept of "UA-private except for transitions" seemed odd at first, but I suppose it could be expressed as adding a UA rule (before the other rules):
Good idea! I think this should work, because transitions are at the highest priority of the cascade. It would also avoid introducing a new "UA private" concept, which is a plus.
Good idea! I think this should work, because transitions are at the highest priority of the cascade. It would also avoid introducing a new "UA private" concept, which is a plus.
I went ahead and edited the original proposal to remove "UA private" and replace with !important
UA style sheet rules. I tested in Chromium and found that transition animations work as expected.
...huh. This seems like an incredibly clever hack. I'm not opposed to it!
When an element is “added to the top layer” (see previous section), it is placed in the ordered set of top-layer elements, but the rendering effect of the top layer (putting it in the top-layer stacking context and obeying the rendering order of the ordered set) only applies when top-layer is set.
I'm unclear on the exact effect of adding this element to the the ordered set but not changing its rendering order. There is a set of rules defined here, the change in rendering order (by updating the element's parent stacking context) is part of it. But isn't all of this deferred by the transition
property which delays updating the top-layer
value?
I thought the concept of "UA-private except for transitions" seemed odd at first, but I suppose it could be expressed as adding a UA rule (before the other rules):
Good idea! I think this should work, because transitions are at the highest priority of the cascade. It would also avoid introducing a new "UA private" concept, which is a plus.
What prevents developers from applying an animation like:
@keyframes foo {
from, to { top-layer: browser }
}
.foo {
animation: foo 1s infinite paused both; /* actually any of these keywords by itself works too */
}
to put elements in the top-layer?
@LeaVerou that'd be in the animations, not transition origin.
@emilio Then transition: 999999999s top-layer
to make it last forever after a given trigger 🤷🏽♀️
My point is that this is a clever hack, but a hack nevertheless, and can be worked around to at least some degree.
The CSS Working Group just discussed Entry and Exit Animations for top-layer elements
.
@emilio Then
transition: 999999999s top-layer
to make it last forever after a given trigger 🤷🏽♀️My point is that this is a clever hack, but a hack nevertheless, and can be worked around to at least some degree.
The browser is still in control of what is in the top layer and its order, which isn't true if exposed as a generic css property. While your example is a hint to the browser that once in the top layer the element should remain in the top layer basically forever, the browser is allowed to still evict it in certain circumstances and can also guarantee that new content shows above the old content (e.g. a new fullscreen element or top layer dialog will go above).
@emilio @LeaVerou I think that problem is gonna stay no matter what solution we take if we acknowledge the use-case. We want developers to be able to keep elements in top layer for the duration of an animation, the length of which should be in developer's control. Whether that happens via :open
/:closed
pseudo-classes or transition: top-layer
, the author is going to be able to keep elements in top layer indefinitely. We could carve out a special duration cap for transitions involving top-layer to address this if needed.
@chrishtr mentioned that was objection around appending things to top layer during the transition. I believe the objection was more around the :top-layer
pseudo and the interactions around it regarding transition (e.g. :top-layer
would be matching even when the popup isn't open). Now that the pseudo-class is named :open
/:closed
, I think this approach can potentially be revisited. (Although feel free to correct me if I'm missing something)
cc @fantasai who brought this up during the call.
Also, I'd like to re-iterate my objection to exposing the name "top layer" explicitly to the web platform.
These are the reasons why:
z-index
issues. It's OK to document it, just like we document UA presentational hints, but I believe it's generally a bad pattern to expose this vocabulary to the language itself.If we want to consider exposing top layer to random elements, I think that's a discussion that needs to happen independently, with a proper discussion around naming, before exposing it to the language.
Also, I'd like to re-iterate my objection to exposing the name "top layer" explicitly to the web platform.
I think another name would be fine. Any ideas? I'll think on it also...
I think this approach can potentially be revisited
@nt1m I'm not sure which approach you're suggesting could be revisited. Could you clarify?
I'm not sure which approach you're suggesting could be revisited. Could you clarify?
The one where we append the element in the top layer when the transition starts, and keeping it in the top layer as long as the transition is still running. I originally objected the :top-layer
pseudo-class interacting with that bit (since it would be misleading), but now that it's :open
/:closed
there's a bit more flexibility around this.
Also, looking at the demo in the first comment, it seems like the issue isn't the top layer itself, but rather the individual adjustments the top layer makes. E.g. this CSS works perfectly:
@keyframes show {
0% {
opacity: 0;
}
}
dialog[open] {
animation: show 400ms;
visibility: visible;
/* UA-default opacity: 1; */
}
@keyframes close {
0% {
visibility: visible;
}
100% {
/* Necessary to repeat display: block unless
interpolation behavior is visibility-like
preferring non-none value. */
visibility: visible;
opacity: 0; }
}
dialog {
--duration: 400ms;
animation: close 400ms;
visibility: hidden;
/* The UA applies the following while dialog is :modal,
we need to preserve it during the animation. */
display: block;
position: fixed;
inset-block-start: 0px;
inset-block-end: 0px;
max-width: calc((100% - 6px) - 2em);
max-height: calc((100% - 6px) - 2em);
}
so it seems to me this is more about allowing the display
CSS property to animate?
Also, if this is about having to repeat all the properties that the top layer sets, then don't we have the same issue with any HTML feature that sets UA styling? It seems to me than this is more a problem tied to display
than top layer.
@nt1m it's not just the UA styles applied in top layer, which developers can replicate. It's also the change in stacking which allows the dialog to paint on top of all other content.
Is this new take on top-layer animation even less friendly than the previous popover one when it comes to non-time based animations?
In previous spec, it was hacky but possible to control the animation from JS by using "proxy" native animations that one could pause and complete.
If I understand correctly, this new proposal is founded on a CSS transition
and that, is impossible to control from JS.
Can someone write a basic example of a physics-based animations (e.g. springs) for a top-layer element removal?
Very disappointing feedback here was not taking in account before sending an "Intent to Ship": https://groups.google.com/a/chromium.org/g/blink-dev/c/nx2P-B8Rhx4/m/QbE3n68fAgAJ
Very disappointing feedback here was not taking in account before sending an "Intent to Ship": https://groups.google.com/a/chromium.org/g/blink-dev/c/nx2P-B8Rhx4/m/QbE3n68fAgAJ
It is not an intent to ship, it's an intent to prototype. We are building a complete prototype in order to make sure we understand all of the implementation touch points of top layer before coming back to the CSSWG for further discussion.
Very disappointing feedback here was not taking in account before sending an "Intent to Ship": groups.google.com/a/chromium.org/g/blink-dev/c/nx2P-B8Rhx4/m/QbE3n68fAgAJ
It is not an intent to ship, it's an intent to prototype. We are building a complete prototype in order to make sure we understand all of the implementation touch points of top layer before coming back to the CSSWG for further discussion.
Ah right, sorry, must have been confused since I saw this coming from https://twitter.com/intenttoship .
Ah right, sorry, must have been confused since I saw this coming from https://twitter.com/intenttoship .
No worries. I agree the intenttoship twitter name is a bit confusing.
In previous spec, it was hacky but possible to control the animation from JS by using "proxy" native animations that one could pause and complete. If I understand correctly, this new proposal is founded on a CSS
transition
and that, is impossible to control from JS.
The getAnimations() API returns all running CSS animations, CSS transitions and web animations allowing them to be modified or controlled from JS. This would let you pause and complete the transition in order to run a JS-based animation.
Can someone write a basic example of a physics-based animations (e.g. springs) for a top-layer element removal?
Something like this (uses a transition of visibility so that it works in regular browsers but with this proposal the same would work for the top layer transition): https://output.jsbin.com/qoyevow
@flackr thanks!
Good thinking, grabbing the CSS transition with getAnimations
. 💡
The code is clunky, but at least it works (any non-keyframe-based animations with native web animations is clunky, so nothing unusual there -- I'm hopeful the platform will make some progress on this front in the future).
Your proof of concept works, but for somewhat more "real" code I'd tweak it a bit:
Listening to transitionstart
is a good idea but then you should just look for transitions that target visibility
(which you know is the key piece here) rather than using evt.propertyName
. In a real application, the dialog might well start other transitions for other states so that would not be reliable.
Just to put the sample code in the issue here, it would look something like this for top-layer:
dialog.addEventListener('transitionstart', (evt) => {
if (evt.propertyName != 'top-layer')
return;
let transition = evt.target.getAnimations().filter((anim) => anim.transitionProperty == evt.propertyName)[0];
transition.pause();
runCustomPhysicsJSAnimation().then(() => { transition.finish(); });
});
You could also roughly (extra work to ignore already running animations not included here) polyfill the previous way this worked by using getAnimations({subtree: true})
to find all animations within the dialog and adjusting the transition duration to match the end time of the last animation, e.g.
dialog.addEventListener('transitionstart', (evt) => {
if (evt.propertyName != 'top-layer')
return;
let transition = evt.target.getAnimations().filter(
(anim) => anim.transitionProperty == evt.propertyName)[0];
let maxDuration = evt.target.getAnimations({subtree: true}).map(
anim => anim.effect.getComputedTiming().endTime).reduce(
(maxDuration, duration) => Math.max(duration, maxDuration), 0);
transition.effect.updateTiming({duration: maxDuration});
});
Here is what I wrote up about spec changes for CSS and HTML that would support transitions of top layer elements with a top-layer property. It matches the implementation we have behind a flag in Blink.
TL;DR Outline spec changes needed to introduce the top layer concept in CSS with a top-layer
property as well as specifying how ordering of top layer candidate elements can be provided by the host languages like HTML.
display: none
subtree.top-layer
property proposal is in issue #8189top-layer
property taking the values none
and browser
.top-layer
property for an element in that ordered list is browser
, render the element in the top layer. If the element is in that list but the top-layer
property computes to none
, do normal rendering as part of the DOM tree.top-layer
is none
transition-property: top-layer
Again, can we come up with a different name than top layer?
Also the values should get better names than none
and browser
, in my opinion.
Sebastian
How about overlay
?
overlay: top
: in the top layer
overlay: normal
: in normal z-index flow
Fullscreen, popover
and dialog
can all be thought of as UI overlays.
overlay
is a lot better, will think more on my side and come back with more suggestions if I find any.
I think overlay: auto
and overlay: none
would be more accurate than top
/normal
since top
won't work on all elements (but only on top layer ones).
Potentially overlay-behavior: auto
makes more sense than overlay: auto
.
This new property is essentially about disabling top layer layout/rendering behaviors during transitions, so I think including -behavior
in there makes sense.
Fwiw, I think top layer is a very bad name here because this property really is about the rendering behaviors that top layer has, rather than the top layer elements list itself. You can have an element in the top layer list, while having those top layer rendering behaviors turned off during transitions using that property.
So overlay-behavior: auto/none
seems to express this better IMO.
@chrishtr I'm trying the top-layer
property in Chrome Canary, and I'm not sure how it works exactly, but my example doesn't seem to work: https://jsfiddle.net/n1p67m0L/
@chrishtr I'm trying the
top-layer
property in Chrome Canary, and I'm not sure how it works exactly, but my example doesn't seem to work: https://jsfiddle.net/n1p67m0L/
A complete working demo is here that you can use on Chrome Canary. Command-line to test:
chrome --user-data-dir=/tmp/foo --enable-blink-features=CSSDisplayAnimation,CSSTransitionDiscrete,CSSTopLayerForTransitions https://codepen.io/chrishtr1/pen/eYLyGgq
This demonstrates a dialog that is normally below a green overlay, but when it is in the top layer it is on top. It also demonstrates animation of discrete properties generally, display
(to prevent disappearing), and top-layer
(to stay on top of the green overlay).
When closing the dialog, it transition-animates opacity, display and top-layer with potentially different durations for each.
I propose that we move forward with a new overlay-behavior
CSS property specified according to the rules described here (except with change of property name of course) and with syntax like this:
overlay-behavior: auto | none
There is the following UA rule:
* {
overlay-behavior: none !important;
}
Note: the only way to override such a UA rule is with transition: overlay-behavior
.
@chrishtr Just wondering, instead of creating a new property, has syncing with display
been considered? Mainly putting this out there since it has been done for inert: https://github.com/w3c/csswg-drafts/issues/8389
@chrishtr Just wondering, instead of creating a new property, has syncing with
display
been considered? Mainly putting this out there since it has been done for inert: #8389
Interesting idea. I think it would work in cases where top layer elements animate to display:none
, but not cases where they don't. e.g. a developer could add dialog { display:block }
or [popover] { display:block }
to their style sheet. In that situation, it's unclear when to remove the element from the top layer when dismissing the dialog or popover, without the UA having to try to guess or detect when animations are happening on dismiss.
Note: the only way to override such a UA rule is with transition: overlay-behavior.
To be clear, that can't override the UA rule. You can't override a UA !important rule, only the UA can override it, so if the UA doesn't apply an overlay-behavior: auto !important
rule to an element it will stay as none
. But the author can control the transition between none
and auto
with such a rule.
From an initial read, I think the proposal makes sense. I think using the term “overlay” is a great improvement over “top-layer”. :) I'm a little uncertain about -behavior as the suffix, it doesn't seem to be behavioral, more like a new positioning scheme. (I wonder if overlay-index: none | auto
makes sense? It's reminiscent of z-index, except the UA is picking the index according to the host rules. Idk)
@chrishtr Just wondering, instead of creating a new property, has syncing with
display
been considered? Mainly putting this out there since it has been done for inert: #8389Interesting idea. I think it would work in cases where top layer elements animate to
display:none
, but not cases where they don't. e.g. a developer could adddialog { display:block }
or[popover] { display:block }
to their style sheet. In that situation, it's unclear when to remove the element from the top layer when dismissing the dialog or popover, without the UA having to try to guess or detect when animations are happening on dismiss.
If your element has display: block
even before the transition, you're going to see a visible shift either way because of all the different adjustments top layer makes: position, containing-block, stacking context, etc. Adding a new property can control the timing of that shift, but cannot remove it. So I don't think this is an use case we should worry too much about.
If your element has
display: block
even before the transition, you're going to see a visible shift either way because of all the different adjustments top layer makes: position, containing-block, stacking context, etc. Adding a new property can control the timing of that shift, but cannot remove it. So I don't think this is an use case we should worry too much about.
In some cases, maybe, but I think preventing the z-index shift during animation will still be useful to developers in these cases. And there's still the problem I mentioned earlier of not being able to easily distinguish between an animation to display:none
vs animation to something else.
The CSS Working Group just discussed [css-animations-2, css-transitions-2] Entry and exit animations for top-layer elements
, and agreed to the following:
RESOLVED: include `overlay` property with values of `auto` and `none` to position-4 with a note about concerns over extensibility
Btw do note that *
does not target every possible selector target. E.g. what happens if I do ::before { overlay: auto }
? Is the plan to add every possible pseudo-element in the UA stylesheet so it can get overlay: initial !important
?
Instead of a new property (which I'm still not a fan of, because it's non-intuitive for web developers), I'm thinking:
z-index
propertydisplay
property (like done for inert)I think this is what probably feels the most natural, and it should be somewhat simple to do this in WebKit. @chrishtr What do you think?
Hi @nt1m,
Can you tell me more about how z-order syncing and adjustments to display
would work? Are you suggesting something that automatically detects animations from developers and syncs to them?
Hi @nt1m,
Can you tell me more about how z-order syncing and adjustments to
display
would work? Are you suggesting something that automatically detects animations from developers and syncs to them?
Have all the non-z-order ones sync with display (display: block = adjust, display: none = do not adjust) in a similar fashion inert is synced with display. Have the z-order ones sync magically with the z-index property (this would probably involve some magic internal value).
Have all the non-z-order ones sync with display (display: block = adjust, display: none = do not adjust) in a similar fashion inert is synced with display. Have the z-order ones sync magically with the z-index property (this would probably involve some magic internal value).
I see. Sounds like the same suggestion you made earlier in the issue? I think my responses here and here are reasons why I think this is not as clean and simple as the overlay
CSS property in semantics, "magic" or reliability for web developers.
I also don't agree that overlay
will be hard for developers to understand, they just need to use the transition
CSS property combined with it, which is straightforward and easy for them to copy-and-paste.
Have all the non-z-order ones sync with display (display: block = adjust, display: none = do not adjust) in a similar fashion inert is synced with display. Have the z-order ones sync magically with the z-index property (this would probably involve some magic internal value).
I see. Sounds like the same suggestion you made earlier in the issue?
Not really, you mentioned developers want to control the z-index
shift separately from display
so my suggestion is to control this separately from the display
property.
I also don't agree that
overlay
will be hard for developers to understand, they just need to use thetransition
CSS property combined with it, which is straightforward and easy for them to copy-and-paste.
I'm not concerned about it being hard to understand. I'm trying to fit top layer into the pre-existing the CSS models, which I do think it leads to a better developer experience it we manage to do so, rather than introducing a new model. E.g. imagine being a web developer knowing nothing about the top layer (which is expected in most cases), but knowing basic CSS, what would be their first intuition to solving the problem?
I think it would be good to think about this first before moving forward with a new property, I'm open to any suggestions that moves towards that direction.
Something like z-index: overlay
which would be a z-index
value only available for the UA to apply? Then developers could add a transition on that which would preserve the overlay setting on exiting. Downside would be that naively other z-index changes would also transition.
Animating an element into and out of the top layer provides context to the user for a more useful and pleasant interaction. Currently, developers have no way to do so in a way that preserves top-layer status during the animation, and as a result, demos like this one won’t work in cases where exiting the top layer causes a visible change of positioning or z-index. Let's fix that.
TL;DR of proposal
Introduce a new CSS property called
top-layer
that can be targeted by web developers in their CSS transitions and animations.This CSS property is not generally available to web developers and will always be set with an
!important
UA style sheet rule. It’s defined as a CSS property for the purpose of a layered definition that fits within existing animations APIs and concepts, and as a result is easy for developers to use with existing animation APIs.Example: a
<dialog>
remaining in the top layer during a 200ms opacity animation during close. The part marked as "New!" is the only addition needed for the developer to delay top-layer removal until the end of the animation.(full demo except for
top-layer
here)Details
Use case
This feature enables support for animation when entering or leaving the top layer. Such animations often wish to preserve top-layer rendering during the course of an “exit” or "entry" animation. Without this feature, it is impossible to perform such an animation.
Top-layer APIs:
<dialog>
, fullscreen, and popover.Note: The demo in the TL;DR uses CSS animations, but a CSS transition would additionally require this issue to be resolved to be usable for entry transition animations. We also need to support animating
display:none
for top layer animations that need it, such as<dialog>
(tracked here).Background on the top layer
The top layer is defined here. It is currently accessed only via the Fullscreen API (here) and the
<dialog>
element’sshowModal
method (here). When opening a fullscreen element, the fullscreen spec says “add it to its node document’s top layer”. When closing fullscreen, it says “remove it from its node document’s top layer”.When calling
showModal
, the<dialog>
spec says “add subject [the dialog element] to subject's node document's top layer”. When closing a dialog, it says “If subject is in its Document's top layer, then remove it”. If an element is already in the top layer, re-adding it to the top layer moves it to the top of the top layer.Proposed Definition of the
top-layer
CSS propertyThe
top-layer
CSS property determines whether an element is in the top layer. It has two values:none
transition
CSS property valuesWhen an element is “added to the top layer” (see previous section), it is placed in the ordered set of top-layer elements, but the rendering effect of the top layer (putting it in the top-layer stacking context and obeying the rendering order of the ordered set) only applies when top-layer is set.
The element is only removed from the ordered set once top-layer’s computed style has evaluated to
none
(at the end of the transition, if any). However, the “moved to the top of the top layer” behavior still occurs if the element is “added to the top layer” while animating out (see previous section).The following UA style rules are added:
Why shouldn't developers be able to change
top-layer
outside of transitions?It’s important to restrict direct developer access because the top layer is a UA-managed resource. Not giving developers direct access guarantees that the UA is able to show top layer content on top of other content, and control eviction from the top layer when needed. This point has been discussed in #6965. If in the future a "developer" top layer is added below the browser one, we could potentially add a third value to
top-layer
.