Open LeaVerou opened 3 years ago
display
does affect CSS animations in the same way as animation properties. Specifically from https://www.w3.org/TR/css-animations-1/#animations
Setting the display property to none will terminate any running animation applied to the element and its descendants. If an element has a display of none, updating display to a value other than none will start all animations applied to the element by the animation-name property, as well as all animations applied to descendants with display other than none.
This means that if a CSS animation animated to display: none
it would immediately cancel the animation, losing the display: none
which would restart the animation.
Ah, bummer. "Not animatable" makes total sense then.
Just for reference, at some point display
was animatable on Firefox, but that could freeze the browser. https://bugzil.la/1264396
I wonder if we could support only specific values of display, e.g. anything but none
on css animations, and possibly anything on web animations since their lifetime is not controlled by the display property. Then you could create an animation which maintains display: block
until it finishes or use a web animation.
Having only some values animateable would be a new concept, but yes, that would work and probably be useful.
Reopening this to consider allowing animations of display to prevent the element from immediately computing to display: none
.
In particular, my straw-man proposal is the following:
display: none
is dropped from keyframes.With these two points, we shouldn't have any circularity. An animation cannot itself produce display: none
since it can't specify it (rule 1). Transitions to display: none
would in theory work since the display: none
wouldn't apply until after the transition finished (rule 2), however since display is a discrete animation it would still skip immediately to none per css-transitions-1 transitionable rule. Issue #4441 explores ideas for relaxing this if discrete properties are explicitly listed.
With this, developers could do things like:
.hide {
transition: opacity 200ms, display 200ms;
display: none;
opacity: 0;
}
Or:
@keyframes slideaway {
from { display: block; }
to { transform: translateY(40px); opacity: 0;}
}
.hide {
animation: slideaway 200ms;
display: none;
}
With this, developers could do things like
Just to add on: this should also work for an "entry" animation from display:none
to display:block
with the same proposed change.
The CSS Working Group just discussed [css-display] Why is display listed as not animatable instead of animation type: discrete?
, and agreed to the following:
RESOLVED: adopt this proposal, and work out the details
It's worth noting that the outer display type can already be discretely animated (indirectly, and with side-effects), e.g. by setting display: inline-grid
outside of animations, and then animating position
/float
to trigger a blockification into display: grid
. See https://github.com/w3c/csswg-drafts/issues/6846#issuecomment-983107012
Comment moved to https://github.com/w3c/csswg-drafts/issues/8389
Chrome 111.0.5545.0 is already supported and requires a Flag:
/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --enable-blink-features=CSSDisplayAnimation
Demo: https://codepen.io/yisi/pen/RwBzqGE
https://user-images.githubusercontent.com/2784308/218027861-5774bf58-f95e-4723-912d-5d59fc8c63fb.mp4
@flackr Edited in your proposal, see https://github.com/w3c/csswg-drafts/commit/b255d549bbb7a9df1975224d2a5b2c2cbc1ac068. One thing I'm not clear on is what happens to display
declarations inside an @keyframes
. It's clear that you intended them to be ignored, but are they dropped at parse time or do they appear in the OM?
display
declarations in @keyframes
would be used as is except for display: none
. The current proposal is that none
values would be replaced by revert
per @emilio's suggestion in https://github.com/w3c/csswg-drafts/issues/6429#issuecomment-1332439874 . We haven't prototyped this yet - see crbug.com/1411474. Alternately we could drop them from the processed keyframes but I'm not sure if that would raise other issues.
Would revert-layer
make more sense?
So I was worried about the circularity of an animation animating to a state that stops the animation which in turn restarts it but we might already have this with content
being animatable on pseudo elements with something like this:
@keyframes no-content {
from { content: 'anim-start'; }
to { content: none; }
}
#target:before {
content: 'visible';
animation: no-content 5s infinite;
}
Demo at: https://jsbin.com/nuwagoc/edit?css,output
When content animates to none
there is no box generated AFAICT.
@flackr A few questions that you may have already thought about:
Rebuilding layout trees is not fast, I'm surprised you want to enable it during an animation.
- What about introducing a new value that's similar to none, except the boxes are skipped during layout/painting/a11y, but not actually destroyed? Then in animations you could have 'display: none' (and 'content: none' on pseudos) behave as that value.
I think this is effectively content-visibility: hidden
, which developers should also be able to do as part of this change (i.e. we should also make content-visibility animatable as discrete interpolation.
- Alternatively, since the main use case seems to be animating to/from 'display: none', introducing a new property (or a new value of 'visibility'?) that flips between 'display me' and 'don't display me' and having that be animatable as discrete. (There's been requests for such a property in the past, to avoid messing with the "which box type" aspect of 'display'.)
If we have interpolation from any value and none
always choose the non-none value (similar to visibility animations) we will have effectively this.
Rebuilding layout trees is not fast, I'm surprised you want to enable it during an animation.
The flip of the value happens once (per keyframe pair that changes the value), this is simply enabling developers to coordinate these property changes with the rest of their animation without having to do the same style change / layout tree rebuild they would have done in a JS listener.
It does seem worth avoiding potential cycles in determining what the state should be - I just thought it was interesting that we already had allowed what is essentially a cycle with content animations. Interestingly on my demo every browser handles it differently:
content: none
and never updates again (e.g. the animation is effectively canceled but none is still applied.content: none
and which removes the pseudo for a frame but then restyles it as the animation override is no longer there restarting the animation. This is the cycle we are trying to avoid by having none
compute to the underlying style.I think this is effectively content-visibility: hidden
No, content-visibility
only affects the content not the box itself. Right?
The flip of the value happens once (per keyframe pair that changes the value)
You said in https://github.com/w3c/csswg-drafts/issues/6429#issuecomment-1453876094 that only display: none
gets ignored, an animation flipping back and forth between inline
and grid
would therefore still be possible.
I agree, it would be a bad idea to animate display
between inline and grid repeatedly, though the value only changes once per visited keyframe so it's still O(# of state changes developer has defined). Typically however, we make properties not animatable for fundamental spec complexity, rather than efficiency arguments. Animating between non-none display values is well defined in how it should apply and I feel like it would just add more spec complexity to try to carve out a special case for a property we already have and developers already use to flip between those states.
OK, edits are in https://drafts.csswg.org/css-display-4/#display-animation
What do you want to do about content: none
? Should we replace it with content: ""
the same way we're replacing display: none
with display: revert-layer
?
[...] a declared value of
none
in an animation effect or@keyframes
rule is replaced withrevert-layer
.
Given the above definition, please correct me if the following expectation is wrong:
<style>
@keyframes myAnimation { to { display: none } }
</style>
<script>
document.styleSheets[0].cssRules[0].cssRules[0].style.display; // 'revert-layer'
</script>
OK, edits are in https://drafts.csswg.org/css-display-4/#display-animation
One slight question - none only interrupts CSS animations, so I think we only need to modify none
values in animations with an owning element.
What do you want to do about
content: none
? Should we replace it withcontent: ""
the same way we're replacingdisplay: none
withdisplay: revert-layer
?
Yeah we should probably do the same with content as it has exactly the same sort of problem and currently has 3 completely different behaviors across browsers. Again, only necessary when the animation has an owning element.
Given the above definition, please correct me if the following expectation is wrong
I think so? @flackr @emilio, is this what you expect?
One slight question - none only interrupts CSS animations, so I think we only need to modify none values in animations with an owning element.
I'm not sure what you're trying to optimize here. Does an animation have any observable effect if it doesn't have an owning element?
Yeah we should probably do the same with content as it has exactly the same sort of problem and currently has 3 completely different behaviors across browsers.
Web animations have no owning element, but still have an effect target to which their animated styles apply. Animations with owning elements continue running as long as their owning element is in the document and displayed, for example if you click Toggle A in this demo the css animation continues to run even though A has display none because its owning element, B, is still in the document. Animations without an owning element continue to run forever.
As such, the only case we need to prevent is an animation setting style such that it stops its own animation - i.e. applies display: none
to its owning element (or an ancestor). What I've proposed is a slight simplification, since I suspect developers changing the effect target of css animations is uncommon but if we wanted to be precise we could say only if the effectTarget is the owning element or an ancestor of it.
@flackr OK. I think I'd like to confirm with the other implementers what is the easiest set of restrictions to implement, whether it's easier to be more targetted about it or to be more broad about it. CC @emilio @smfr
Question: We decided to replace display: none
in keyframes with display: revert-layer
in order to avoid an animation setting a style that stops its own animation. When/where/under what conditions exactly should this replacement take place?
See also follow-up issues on content
and content-visibility
:
Though it's not a strong preference, I personally prefer the alternative approach in https://github.com/w3c/csswg-drafts/issues/6429#issuecomment-1462399688 . The example of CSS animation in https://github.com/w3c/csswg-drafts/issues/6429#issuecomment-1318933547 looks awkward to me. Given that to animate the display property web authors need to specify at least a display property value in a keyframe, even if it's the same right? For example, the example can be written;
@keyframes slideaway {
from { display: flex; }
to { transform: translateY(40px); opacity: 0;}
}
#target {
display: flex;
}
#target.hide {
animation: slideaway 200ms;
display: none;
}
Web authors will probably need to care that they need to specify the same display value. Am I missing something?
Okay so, web authors normally will write something like;
@keyframes slideaway {
to { transform: translateY(40px); opacity: 0; display: none:}
}
#target.hide {
animation: slideaway 200ms;
display: none;
}
This doesn't look awkward. :)
Instead of removing animations when set to none, remove animations when the computed value resolves to none. A developer could animate display by setting a transition with a step-end easing on the display property. But also, create a class with an animation on the display property with keyframes from block to block. The animation should have a higher composite order than the transition on the same element so it overrides the transition. Animations would still need to be triggered when the specified value of display is set, instead of the computed value which is kind of arbitrary. But it doesn't have to be additive, and doesn't require adding new keywords like inert https://github.com/w3c/csswg-drafts/issues/8389
So when I was experimenting with animating content
it seemed like Firefox didn't stop animating the pseudo-element even when content was none. This got me wondering whether we could only remove animations if the base computed display style AND the computed display style is none. This might I think fix the circularity issues and allow the feature to work the simple way that a developer would expect.
Replying to https://github.com/w3c/csswg-drafts/issues/6429#issuecomment-1484270623
Though it's not a strong preference, I personally prefer the alternative approach in #6429 (comment) .
I'm not opposed to having such a property but I think it would be a bit challenging that until the new property had universal support you'd still have to specify display: none
when the new property wasn't supported. e.g.
.target.hidden {
display: none;
}
@supports(visibility: none) {
.target.hidden {
transition: visibility;
visibility: none;
display: block;
}
}
Technically you could use @supports not (visibility: none)
but this was historically considered bad practice. However, since all of this can be done orthogonally I still feel like it makes sense to justallow animating display now and we could pursue this other property later for better ergonomics.
The example of CSS animation in #6429 (comment) looks awkward to me. Given that to animate the display property web authors need to specify at least a display property value in a keyframe, even if it's the same right?
The simple thing would be to transition display, but yes, to do it with an animation you have to repeat the same value. This is no different than how you have to repeat the old value of any other property if you want to use an animation to go from the old value to some new value, e.g.
@keyframes fade-out {
/* you have to specify the old value because it is no longer applied by the cascade. */
from {opacity: 1;}
}
.target.hide {
opacity: 0;
animation: fade-out 200ms;
}
For example, the example can be written;
@keyframes slideaway { from { display: flex; } to { transform: translateY(40px); opacity: 0;} } #target { display: flex; } #target.hide { animation: slideaway 200ms; display: none; }
Web authors will probably need to care that they need to specify the same display value. Am I missing something?
The simpler approach would be to add transition: display 200ms
to the #target.hide
selector. But yes, having to specify the old value if you want to animate from a previously applied value is standard for any property.
Okay so, web authors normally will write something like;
@keyframes slideaway { to { transform: translateY(40px); opacity: 0; display: none:} } #target.hide { animation: slideaway 200ms; display: none; }
This doesn't look awkward. :)
@hiikezoe this doesn't work. The implicit from
keyframe uses the underlying value for display, which is none
from the #target.hide
rule. transition: display 200ms
however would work.
Okay so, web authors normally will write something like;
@keyframes slideaway { to { transform: translateY(40px); opacity: 0; display: none:} } #target.hide { animation: slideaway 200ms; display: none; }
This doesn't look awkward. :)
@hiikezoe this doesn't work. The implicit
from
keyframe uses the underlying value for display, which isnone
from the#target.hide
rule.transition: display 200ms
however would work.
Oops, you are right. :/
I think I'd like to confirm with the other implementers what is the easiest set of restrictions to implement, whether it's easier to be more targetted about it or to be more broad about it.
So when I was experimenting with animating content it seemed like Firefox didn't stop animating the pseudo-element even when content was none. This got me wondering whether we could only remove animations if the base computed display style AND the computed display style is none. This might I think fix the circularity issues and allow the feature to work the simple way that a developer would expect.
I have implemented both display:none->display:revert and flackr's idea here of not canceling the animation when the animation sets the style to display:none. Unless there are edge cases I'm not thinking of, both were similar in difficulty to implement. I don't have a preference yet of which we should go with, but I defer to flackr's judgement.
To implement display:none->display:revert, I modified the style resolution code in parts where it knows that the style is being applied for an animation to change display:none to display:revert. It required additional changes in another spot to handle this case with variables:
@keyframes hello {
0% { --display-value: none; }
100% { --display-value: none; }
}
#target {
display: var(--display-value, block);
}
#target.animation {
animation-name: hello;
animation-duration: 1s;
}
To implement not canceling the animation when an animation sets display:none, I used similar spots in the style resolution code but set a flag on the element when the animation sets display:none, so that the element knows that it shouldn't cancel the animation right after it recalcs its own style.
Not canceling the animation provides the ability to animate to display:none and back with keyframes during an animation and actually make the element disappear, but I don't know if that's a use case that we need to support. Here is an example of what that would look like:
@keyframes {
0% { display: block; }
20% { display: none; }
80% { display: none; }
100% { display: block; }
}
Display is interpolated like visibility, i.e. it will prefer the non-none value.
Due to this visibility
-like behavior, none of the following use cases will be affected by the display:none->display:revert or don't cancel the animation behavior because there has to be a full interpolation step to and from display:none. All of these examples will be display:block for the entire duration of the animation regardless of which implementation option we choose.
@keyframes {
0% { display: block; }
100% { display: none; }
}
@keyframes {
0% { display: none; }
100% { display: block; }
}
#target {
display: block;
transition: display 1s;
}
#target.animated { display: none; }
#target {
display: none;
transition: display 1s;
}
#target.animated { display: block; }
Thanks @josepharhar for the implementation experience!
I believe that changing the cancellation rules for css animations to be only when the base computed style and the animated display style both produce none
is much simpler to understand and explain, so I'd like to propose going with this since the implementation experience suggests that this works (and also that it has fewer special cases).
This is also consistent with the way that animations of content
to none on pseudo-elements behave in Firefox today (animation keeps running), and conceptually similar to animating visibility.
To implement not canceling the animation when an animation sets display:none, I used similar spots in the style resolution code but set a flag on the element when the animation sets display:none, so that the element knows that it shouldn't cancel the animation right after it recalcs its own style.
the implementation experience suggests that this works (and also that it has fewer special cases).
Yeah it turns out that not canceling the animation is much easier to implement, I actually don't need to mess with the style resolution code at all that I mentioned earlier like I did in order to implement the display:revert behavior
There is no distinction between computed and animated values, is there? Aren't they one and the same? I asked for this distinction a decade ago in public-fx, because the pattern I call relative animation doesn't need current animated values for interruption. It only ever needs discrete values. Also, the underlying value is not necessarily discrete, which also confused me ten years ago.
Is or is not the comment I made almost eight hours before flackr the same solution?
This is what I wrote: https://github.com/w3c/csswg-drafts/issues/6429#issuecomment-1486246609
Instead of removing animations when set to none, remove animations when the computed value resolves to none.
This is what flackr wrote less than eight hours later: https://github.com/w3c/csswg-drafts/issues/6429#issuecomment-1486895943
This got me wondering whether we could only remove animations if the base computed display style AND the computed display style is none.
And then yesterday: https://github.com/w3c/csswg-drafts/issues/6429#issuecomment-1503849651
only when the base computed style and the animated display style both produce
none
Can someone please explain the difference? If not, please make the distinction between discrete and animated computed values in specification. Is there a new definition in css-cascade-4, css-cascade-5, or css-cascade-6? They're hard to read since they are in some .bs format (no pun intended).
Another question, will something like this be possible? https://github.com/w3c/csswg-drafts/issues/6429#issuecomment-1486246609
animate display by setting a transition with a step-end easing on the display property. But also, create a class with an animation on the display property with keyframes from block to block.
It would make a lot of sense for front-end developers:
@keyframes displaying {
0% { display: block; }
100% { display: block; }
}
#target {
transition: display 1s step-end, opacity 1s linear;
animation: displaying 1s linear;
}
.in {
display: block;
opacity: 1;
}
.out {
display: none;
opacity: 0;
}
Would this work? Can we please make it work? If not, why?
I think my code suggestion fails on interruption, animating in, then reversing back out. Or, animation and transition rules would need to be different, which isn't good. Still would like discrete and animated computed values, hopefully someone else will too.
There is no distinction between computed and animated values, is there? Aren't they one and the same? I asked for this distinction a decade ago in public-fx, because the pattern I call relative animation doesn't need current animated values for interruption. It only ever needs discrete values. Also, the underlying value is not necessarily discrete, which also confused me ten years ago.
Is or is not the comment I made almost eight hours before flackr the same solution?
This is what I wrote: #6429 (comment)
Instead of removing animations when set to none, remove animations when the computed value resolves to none.
This is what flackr wrote less than eight hours later: #6429 (comment)
This got me wondering whether we could only remove animations if the base computed display style AND the computed display style is none.
And then yesterday: #6429 (comment)
only when the base computed style and the animated display style both produce
none
Can someone please explain the difference? If not, please make the distinction between discrete and animated computed values in specification. Is there a new definition in css-cascade-4, css-cascade-5, or css-cascade-6? They're hard to read since they are in some .bs format (no pun intended).
The base word is the key difference here. This is referring to the style before the animations and transitions cascade. The computed value will resolve to none
if we allow the animation to include display: none
in its keyframes which then leads to the problematic circularity.
However, if the value before the animations part of the cascade is also none
then when the computed value becomes none
canceling the animation will not restart the animation.
I've taken a pass at writing the spec updates for this in https://github.com/w3c/csswg-drafts/pull/8713
At the risk of sealioning, I just want to say this will probably not work as is with my (experimental and unapproved by the W3C) desired changes for discrete state: https://phabricator.services.mozilla.com/D156634#5139009
These are flackr's changesets and diffs for my or anyone else's future reference: https://chromium-review.googlesource.com/c/chromium/src/+/4356155 https://chromium.googlesource.com/chromium/src/+/40601f60e9d413ca1fad4891041c0f43925a7f7e%5E%21/ https://chromium-review.googlesource.com/c/chromium/src/+/4363078 https://chromium.googlesource.com/chromium/src/+/1cc90dd5bb6ba48e9b359d3e93e8223e7eae98de%5E%21/
At the risk of sealioning, I just want to say this will probably not work as is with my (experimental and unapproved by the W3C) desired changes for discrete state: https://phabricator.services.mozilla.com/D156634#5139009
Can you be more specific? In particular,
Can you be more specific?
I've been incapacitated, again, but will try to make a coherent proposal to sustyweb.
The CSS Working Group just discussed [css-display] Why is display listed as not animatable instead of animation type: discrete?
, and agreed to the following:
RESOLVED: When an animations produces display none it continues to run but it still cancels animations within that subtree.
https://www.w3.org/TR/css-display-3/#the-display-properties
I thought we were going for discrete for all properties that can't be intelligently interpolated, and that "not animatable" was reserved primarily for animation properties and the like?
Especially about
display
, there are plenty of use cases where authors want to finish an animation withdisplay: none
, and this prevents them from doing so.