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

[Popup] Supporting transitions and animations on open and dismiss #335

Closed
flackr opened this issue Apr 15, 2021 · 22 comments
Closed

[Popup] Supporting transitions and animations on open and dismiss #335

flackr opened this issue Apr 15, 2021 · 22 comments
Labels
CSSWG Issues that have a relationship with CSS needs edits This is ready for edits to be made open-ui-resolved-accepted popover The Popover API

Comments

@flackr
Copy link

flackr commented Apr 15, 2021

There has been some discussion about whether there should be a pseudoclass for the open state, and whether we can support animations on popup. Naively, if popup's show immediately applies display: block and a :visible (name TBD) pseudoclass and popup hide immediately removes both it is only possible to show an animation on open, and a transition would not work.

If we shift the timing of the display: block / none update from the pseudoclass state change we could allow transitions and animations to run. Specifically, on opening we could have a style update before :visible is applied which would trigger any transition properties. On closing, we would wait for the finished promise of any running animations before setting display: none.

This is a demo of the proposed behavior implemented in Javascript showing how this works with both css animations and css transitions: https://jsbin.com/piqebiw/edit?css,js,output

@ghost
Copy link

ghost commented Apr 15, 2021

I'm new here and I may be completely out of line, but this made me think of something that might work for multiple use cases.

Following the lead of the Paint, Layout, and Animation worklets; we could have an "Event Worklet" that could be applied to something like onChanging: 'Display' eventWorklet; or onDisplayChanging: eventWorklet;.

onChanging would be triggered just before the property changed and if a promise is returned from the worklet then the actual value change waits for it to return. If there is no promise returned then we get an immediate change. This behaviour would keep backwards compatibility but also give developers/designers the chance to intervene if they need to. There could also be an onChanged event for after the value changes enabling easier transitions for after a display: block is triggered.

Animation events can also be used to do this kind of thing, but it requires a bit of setup to do properly. Generally I will apply an intermediate class that starts the animation and then I would use the animationend event to either remove or add display: none on the element.

@una una added the agenda+ Use this label if you'd like the topic to be added to the meeting agenda label May 12, 2021
@flackr
Copy link
Author

flackr commented May 13, 2021

I'm new here and I may be completely out of line, but this made me think of something that might work for multiple use cases.

Following the lead of the Paint, Layout, and Animation worklets; we could have an "Event Worklet" that could be applied to something like onChanging: 'Display' eventWorklet; or onDisplayChanging: eventWorklet;.

onChanging would be triggered just before the property changed and if a promise is returned from the worklet then the actual value change waits for it to return. If there is no promise returned then we get an immediate change. This behaviour would keep backwards compatibility but also give developers/designers the chance to intervene if they need to. There could also be an onChanged event for after the value changes enabling easier transitions for after a display: block is triggered.

There was an "apply hooks" proposal as the style part of houdini. I'm not sure where it's at today but it supported running code in response to style changes.

The catch was that the only output of an apply hook is changing other styles. You have to be very careful not to allow cycles, where arbitrary modification before an update takes place could easily trigger cycles. The houdini apply hooks proposal detected and prevented cycles at registration time.

@gregwhitworth
Copy link
Member

This was discussed in today's telecon with the following resolution:

RESOLUTION: popups have an intermediate state where they are display: block but not yet :open which they transition to when showing / hiding. On hiding, the UA will wait for ongoing animations before setting display: none.

Minutes are here

@gregwhitworth gregwhitworth added open-ui-resolved-accepted needs edits This is ready for edits to be made CSSWG Issues that have a relationship with CSS and removed agenda+ Use this label if you'd like the topic to be added to the meeting agenda labels May 13, 2021
@github-actions
Copy link

There hasn't been any discussion on this issue for a while, so we're marking it as stale. If you choose to kick off the discussion again, we'll remove the 'stale' label.

@github-actions github-actions bot added the stale label Mar 19, 2022
@mfreed7
Copy link
Collaborator

mfreed7 commented Apr 8, 2022

With the new Popup proposal (#455), the UA does not force-hide the popup when it is not "showing". The UA has a display: none rule without !important, meaning it can be overridden. Given that behavior, popups can be animated in both directions:

  • Animating the "show" is easy - just add an animation in a :popup-open pseudo selector:
[popup]:popup-open {
  animation: {whatever you like};
}
  • Animating the "hide" requires Javascript, but not too much:
<script>
popup.addEventListener('hide',(e) => {
  e.target.classList.add('animating-out');
  e.target.addEventListener('animationend',() => {
    e.target.classList.remove('animating-out');
  }, {once: true});
});
</script>

<style>
[popup]:not(:popup-open).animating-out {
  display: block; /* Override display:none while animating */
  animation: {whatever you like};
}
</style>

Do folks think this is "enough" to resolve this issue?

Side note: this issue relates to #342.

@mfreed7
Copy link
Collaborator

mfreed7 commented May 18, 2022

I've been giving this some thought. Increasingly, I'm thinking the solution in my comment just above is not enough. It has three main limitations:

  1. Requires javascript.
  2. Transitions are not supported, only animations.
  3. Can break for popups contained in containing blocks - as soon as the popup is removed from the top layer, it'll be contained.

I think the solution presented in the OP could work, with potentially one modification. The case I'm concerned about is the "popup forced out of the top layer" case. For example when a modal <dialog> or fullscreen element opens, all popups should be immediately hidden, or at least removed from the top layer. I wouldn't want that operation to get indefinitely delayed by a hide animation on a popup. So what about this for logic:

showPopup():

  1. Move the popup to the top layer, and remove the UA display:none style.
  2. Update style. (Transitions can start from this state.)
  3. Set the :top-layer or (whatever) pseudo class.
  4. Update style. (Animations/transitions happen here.)

hidePopup():

  1. Remove the :top-layer pseudo class.
  2. Update style. (Animations/transitions start here.)
  3. If the hidePopup() call is not due to a "force out" situation, wait at this step until all finished promises on popup.getAnimations() resolve.
  4. Remove the popup from the top layer, and add the UA display:none style.
  5. Update style.

@flackr did I get the above right? Anything missing?

@flackr
Copy link
Author

flackr commented May 18, 2022

I think your latest proposed solution sounds good. I was worried about the same points you raised.

This animate / transition to / from display: none touches on a much more general issue that setting up animations like this is complicated. Though in this case I think some additional complexity would be necessary regardless to maintain top layer to avoid popping (your third point above).

I'd be happy to go with the proposal above, but we should consider if there's some way to support entry / exit animations for other elements that come into existence / go away. w3c/csswg-drafts#6429 is somewhat related. An example terrible idea, there could be a pseudoclass that when it first starts matching forces an additional style update after the first style update in which it then applies?

@mfreed7
Copy link
Collaborator

mfreed7 commented May 26, 2022

I think your latest proposed solution sounds good. I was worried about the same points you raised.

Thanks!

I'd be happy to go with the proposal above, but we should consider if there's some way to support entry / exit animations for other elements that come into existence / go away. w3c/csswg-drafts#6429 is somewhat related. An example terrible idea, there could be a pseudoclass that when it first starts matching forces an additional style update after the first style update in which it then applies?

Hmm, I would love it if this could be solved more generally, to avoid the specific complexity within Popup. Do you think w3c/csswg-drafts#6429 might be resolved any time soon? I want to avoid blocking on that, since I think this behavioral change would be fairly significant and would be a breaking change if made later.

@mfreed7 mfreed7 added the agenda+ Use this label if you'd like the topic to be added to the meeting agenda label May 26, 2022
@css-meeting-bot
Copy link

The Open UI Community Group just discussed How are we going to support animations for popups.

The full IRC log of that discussion <gregwhitworth> Topic: How are we going to support animations for popups
<gregwhitworth> masonf: there is a proposal
<gregwhitworth> masonf: there is a change in behavior in popup to make it trivial to add animations/transitions to open and close with no JS
<gregwhitworth> masonf: there are open questions
<gregwhitworth> masonf: I opened an issue in top-layer and they raised discussion about animations and when would it match vs not
<masonf> github: https://github.com//issues/335
<gregwhitworth> masonf: so please see the issue 335
<gregwhitworth> flackr: I was going to add that the idea of waiting for animations to finish is not novel it's being used for sharedElementTransitions
<tantek> for the minutes: https://github.com/w3c/csswg-drafts/issues/7319
<gregwhitworth> masonf: I've also started implementing it and it looks better than I thought it would
<JonathanNeal> For future prototype purposes, is there general guidance on how to implement animation waiting?
<gregwhitworth> Zakim, end meeting
<Zakim> As of this point the attendees have been flackr, gregwhitworth, dandclark, scotto_, una, masonf, miriam, tantek, emilio, JonathanNeal
<Zakim> RRSAgent, please draft minutes
<RRSAgent> I have made the request to generate https://www.w3.org/2022/06/02-openui-minutes.html Zakim
<Zakim> I am happy to have been of service, gregwhitworth; please remember to excuse RRSAgent. Goodbye
<gregwhitworth> RRSAgent, please leave
<RRSAgent> I see no action items

chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue Jun 3, 2022
Now, popups will follow this process when showing/hiding:

showPopup():
 1. Move the popup to the top layer, and remove the UA display:none
    style.
 2. Update style. (Transitions can start from this state.)
 3. Set the :top-layer or (whatever) pseudo class.
 4. Update style. (Animations/transitions happen here.)

hidePopup():
 1. Remove the :top-layer pseudo class.
 2. Update style. (Animations/transitions start here.)
 3. If the hidePopup() call is not due to a "force out" situation,
    wait at this step until all finished promises on
    popup.getAnimations() resolve.
 4. Remove the popup from the top layer, and add the UA display:none
    style.
 5. Update style.

See this issue for more details:
  openui/open-ui#335

Bug: 1307772
Change-Id: Ia20eb6e9533c1a0b1029ca1279d42fe2648300af
@gregwhitworth gregwhitworth removed the agenda+ Use this label if you'd like the topic to be added to the meeting agenda label Jun 7, 2022
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue Jun 7, 2022
Now, popups will follow this process when showing/hiding:

showPopup():
 1. Move the popup to the top layer, and remove the UA display:none
    style.
 2. Update style. (Transitions can start from this state.)
 3. Set the :top-layer or (whatever) pseudo class.
 4. Update style. (Animations/transitions happen here.)

hidePopup():
 1. Remove the :top-layer pseudo class.
 2. Update style. (Animations/transitions start here.)
 3. If the hidePopup() call is not due to a "force out" situation,
    wait at this step until all finished promises on
    popup.getAnimations() resolve.
 4. Remove the popup from the top layer, and add the UA display:none
    style.
 5. Update style.

See this issue for more details:
  openui/open-ui#335

Bug: 1307772
Change-Id: Ia20eb6e9533c1a0b1029ca1279d42fe2648300af
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue Jun 7, 2022
Now, popups will follow this process when showing/hiding:

showPopup():
 1. Move the popup to the top layer, and remove the UA display:none
    style.
 2. Update style. (Transitions can start from this state.)
 3. Set the :top-layer or (whatever) pseudo class.
 4. Update style. (Animations/transitions happen here.)

hidePopup():
 1. Remove the :top-layer pseudo class.
 2. Update style. (Animations/transitions start here.)
 3. If the hidePopup() call is not due to a "force out" situation,
    wait at this step until all finished promises on
    popup.getAnimations() resolve.
 4. Remove the popup from the top layer, and add the UA display:none
    style.
 5. Update style.

See this issue for more details:
  openui/open-ui#335

Bug: 1307772
Change-Id: Ia20eb6e9533c1a0b1029ca1279d42fe2648300af
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue Jun 9, 2022
Now, popups will follow this process when showing/hiding:

showPopup():
 1. Move the popup to the top layer, and remove the UA display:none
    style.
 2. Update style. (Transition initial style can be specified in this
    state.)
 3. Set the :top-layer pseudo class.
 4. Update style. (Animations/transitions happen here.)

hidePopup():
 1. Capture any already-running animations via getAnimations().
 2. Remove the :top-layer pseudo class.
 3. Update style. (Animations/transitions start here.)
 4. If the hidePopup() call is not due to a "force out" situation,
    getAnimations() again, remove any from step #1, and then wait here
    until all of them finish or are cancelled.
 4. Remove the popup from the top layer, and add the UA display:none
    style.
 5. Update style.

See this issue for more details:
  openui/open-ui#335

Bug: 1307772
Change-Id: Ia20eb6e9533c1a0b1029ca1279d42fe2648300af
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue Jun 9, 2022
Now, popups will follow this process when showing/hiding:

showPopup():
 1. Move the popup to the top layer, and remove the UA display:none
    style.
 2. Update style. (Transition initial style can be specified in this
    state.)
 3. Set the :top-layer pseudo class.
 4. Update style. (Animations/transitions happen here.)

hidePopup():
 1. Capture any already-running animations via getAnimations().
 2. Remove the :top-layer pseudo class.
 3. Update style. (Animations/transitions start here.)
 4. If the hidePopup() call is not due to a "force out" situation,
    getAnimations() again, remove any from step #1, and then wait here
    until all of them finish or are cancelled.
 4. Remove the popup from the top layer, and add the UA display:none
    style.
 5. Update style.

See this issue for more details:
  openui/open-ui#335

Bug: 1307772
Change-Id: Ia20eb6e9533c1a0b1029ca1279d42fe2648300af
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue Jun 10, 2022
Now, popups will follow this process when showing/hiding:

showPopup():
 1. Move the popup to the top layer, and remove the UA display:none
    style.
 2. Update style. (Transition initial style can be specified in this
    state.)
 3. Set the :top-layer pseudo class.
 4. Update style. (Animations/transitions happen here.)

hidePopup():
 1. Capture any already-running animations via getAnimations().
 2. Remove the :top-layer pseudo class.
 3. Update style. (Animations/transitions start here.)
 4. If the hidePopup() call is not due to a "force out" situation,
    getAnimations() again, remove any from step #1, and then wait here
    until all of them finish or are cancelled.
 4. Remove the popup from the top layer, and add the UA display:none
    style.
 5. Update style.

See this issue for more details:
  openui/open-ui#335

Bug: 1307772
Change-Id: Ia20eb6e9533c1a0b1029ca1279d42fe2648300af
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue Jun 13, 2022
Now, popups will follow this process when showing/hiding:

showPopup():
 1. Move the popup to the top layer, and remove the UA display:none
    style.
 2. Update style. (Transition initial style can be specified in this
    state.)
 3. Set the :top-layer pseudo class.
 4. Update style. (Animations/transitions happen here.)

hidePopup():
 1. Capture any already-running animations via getAnimations().
 2. Remove the :top-layer pseudo class.
 3. Update style. (Animations/transitions start here.)
 4. If the hidePopup() call is not due to a "force out" situation,
    getAnimations() again, remove any from step #1, and then wait here
    until all of them finish or are cancelled.
 4. Remove the popup from the top layer, and add the UA display:none
    style.
 5. Update style.

See this issue for more details:
  openui/open-ui#335

Bug: 1307772
Change-Id: Ia20eb6e9533c1a0b1029ca1279d42fe2648300af
aarongable pushed a commit to chromium/chromium that referenced this issue Jun 15, 2022
Now, popups will follow this process when showing/hiding:

showPopup():
 1. Move the popup to the top layer, and remove the UA display:none
    style.
 2. Update style. (Transition initial style can be specified in this
    state.)
 3. Set the :top-layer pseudo class.
 4. Update style. (Animations/transitions happen here.)

hidePopup():
 1. Capture any already-running animations via getAnimations().
 2. Remove the :top-layer pseudo class.
 3. Update style. (Animations/transitions start here.)
 4. If the hidePopup() call is not due to a "force out" situation,
    getAnimations() again, remove any from step #1, and then wait here
    until all of them finish or are cancelled.
 4. Remove the popup from the top layer, and add the UA display:none
    style.
 5. Update style.

See this issue for more details:
  openui/open-ui#335

Bug: 1307772
Change-Id: Ia20eb6e9533c1a0b1029ca1279d42fe2648300af
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3688871
Reviewed-by: Robert Flack <flackr@chromium.org>
Commit-Queue: Mason Freed <masonf@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1014235}
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue Jun 15, 2022
Now, popups will follow this process when showing/hiding:

showPopup():
 1. Move the popup to the top layer, and remove the UA display:none
    style.
 2. Update style. (Transition initial style can be specified in this
    state.)
 3. Set the :top-layer pseudo class.
 4. Update style. (Animations/transitions happen here.)

hidePopup():
 1. Capture any already-running animations via getAnimations().
 2. Remove the :top-layer pseudo class.
 3. Update style. (Animations/transitions start here.)
 4. If the hidePopup() call is not due to a "force out" situation,
    getAnimations() again, remove any from step #1, and then wait here
    until all of them finish or are cancelled.
 4. Remove the popup from the top layer, and add the UA display:none
    style.
 5. Update style.

See this issue for more details:
  openui/open-ui#335

Bug: 1307772
Change-Id: Ia20eb6e9533c1a0b1029ca1279d42fe2648300af
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3688871
Reviewed-by: Robert Flack <flackr@chromium.org>
Commit-Queue: Mason Freed <masonf@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1014235}
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue Jun 15, 2022
Now, popups will follow this process when showing/hiding:

showPopup():
 1. Move the popup to the top layer, and remove the UA display:none
    style.
 2. Update style. (Transition initial style can be specified in this
    state.)
 3. Set the :top-layer pseudo class.
 4. Update style. (Animations/transitions happen here.)

hidePopup():
 1. Capture any already-running animations via getAnimations().
 2. Remove the :top-layer pseudo class.
 3. Update style. (Animations/transitions start here.)
 4. If the hidePopup() call is not due to a "force out" situation,
    getAnimations() again, remove any from step #1, and then wait here
    until all of them finish or are cancelled.
 4. Remove the popup from the top layer, and add the UA display:none
    style.
 5. Update style.

See this issue for more details:
  openui/open-ui#335

Bug: 1307772
Change-Id: Ia20eb6e9533c1a0b1029ca1279d42fe2648300af
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3688871
Reviewed-by: Robert Flack <flackr@chromium.org>
Commit-Queue: Mason Freed <masonf@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1014235}
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this issue Jun 21, 2022
Automatic update from web-platform-tests
Make popups animation-friendly

Now, popups will follow this process when showing/hiding:

showPopup():
 1. Move the popup to the top layer, and remove the UA display:none
    style.
 2. Update style. (Transition initial style can be specified in this
    state.)
 3. Set the :top-layer pseudo class.
 4. Update style. (Animations/transitions happen here.)

hidePopup():
 1. Capture any already-running animations via getAnimations().
 2. Remove the :top-layer pseudo class.
 3. Update style. (Animations/transitions start here.)
 4. If the hidePopup() call is not due to a "force out" situation,
    getAnimations() again, remove any from step #1, and then wait here
    until all of them finish or are cancelled.
 4. Remove the popup from the top layer, and add the UA display:none
    style.
 5. Update style.

See this issue for more details:
  openui/open-ui#335

Bug: 1307772
Change-Id: Ia20eb6e9533c1a0b1029ca1279d42fe2648300af
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3688871
Reviewed-by: Robert Flack <flackr@chromium.org>
Commit-Queue: Mason Freed <masonf@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1014235}

--

wpt-commits: 297b9403e0ab65348de01169d1a4e3cf078af7b9
wpt-pr: 34304
jamienicol pushed a commit to jamienicol/gecko that referenced this issue Jun 23, 2022
Automatic update from web-platform-tests
Make popups animation-friendly

Now, popups will follow this process when showing/hiding:

showPopup():
 1. Move the popup to the top layer, and remove the UA display:none
    style.
 2. Update style. (Transition initial style can be specified in this
    state.)
 3. Set the :top-layer pseudo class.
 4. Update style. (Animations/transitions happen here.)

hidePopup():
 1. Capture any already-running animations via getAnimations().
 2. Remove the :top-layer pseudo class.
 3. Update style. (Animations/transitions start here.)
 4. If the hidePopup() call is not due to a "force out" situation,
    getAnimations() again, remove any from step #1, and then wait here
    until all of them finish or are cancelled.
 4. Remove the popup from the top layer, and add the UA display:none
    style.
 5. Update style.

See this issue for more details:
  openui/open-ui#335

Bug: 1307772
Change-Id: Ia20eb6e9533c1a0b1029ca1279d42fe2648300af
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3688871
Reviewed-by: Robert Flack <flackr@chromium.org>
Commit-Queue: Mason Freed <masonf@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1014235}

--

wpt-commits: 297b9403e0ab65348de01169d1a4e3cf078af7b9
wpt-pr: 34304
@mfreed7
Copy link
Collaborator

mfreed7 commented Jun 28, 2022

Ok, I've now finished prototyping something in Chromium (thanks for help, @flackr!), and I think it looks pretty good. You can do this now:

[popup] {
  opacity: 0;
  transition: opacity 0.5s;
}
[popup]:top-layer {
  opacity: 1;
}

...and all pop-ups on the page will fade in and out. No Javascript needed.

To accomplish this, here is what showPopUp() and hidePopUp() need to look like:

showPopup():

  1. Move the popup to the top layer, and remove the UA display:none style.
  2. Update style. (Transition initial style can be specified in this state.)
  3. Set the :top-layer pseudo class.
  4. Update style. (Animations/transitions happen here.)
  5. Focus the first contained element with autofocus, if any.
  6. Fire the show event, synchronously.

hidePopup():

  1. Capture any already-running animations on the Element via getAnimations(), including animations on descendent elements.
  2. Stop matching the :top-layer pseudo class.
  3. If the hidePopup() call is the result of the pop-up being "forced out" of the top layer, e.g. by a modal dialog, fullscreen element, or the element being removed from the document:
    a. Queue the hide event (asynchronous).
    Otherwise,
    a. Fire the hide event, synchronously.
    a. Restore focus to the previously-focused element.
    b. Update style. (Animations/transitions start here.)
    c. Call getAnimations() again, remove any that were found in step #1, and then wait until all of the remaining animations finish or are cancelled.
  4. Remove the pop-up from the top layer, and add the UA display:none style.
  5. Update style.

There are also provisions for force-hiding any in-progress hide animations if needed, such as if a modal dialog is later opened.

Note that as part of the above, I changed both show and hide events to synchronously fired events, which seems better overall. This logic also allows animations to be started from the hide event, e.g.

  popUp.addEventListener('hide', () => {
    popUp.animate({transform: 'translateY(-50px)'}, 200);
  });

One potentially "different" thing is that with the above logic, the pop-up will have periods (during animations) where it a) is in the top layer, but b) doesn't match the :top-layer pseudo class. This is required in order that transitions can be triggered by matching :top-layer. But this was raised on w3c/csswg-drafts#7319 (comment) as a potential for confusion. It also raises the question of whether there needs to be even another pseudo class for the transition state (e.g. :top-layer-transitioning).

Thoughts appreciated. This should be testable in a Chromium browser with Experimental Web Platform Features enabled, and a very recent Canary build (like the one that comes out tomorrow). @jh3y

@mfreed7 mfreed7 added the agenda+ Use this label if you'd like the topic to be added to the meeting agenda label Jul 3, 2022
@jh3y
Copy link
Collaborator

jh3y commented Jul 5, 2022

Without the extra pseudo for say [popup]:showing/[popup]:hiding, how would you create a different entrance and exit animation without intervening with JavaScript?

[popup] {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) translateX(calc(var(--x, -1) * 100vh));
  transition: transform 0.25s ease-out;
}

[popup]:top-layer {
  transform: translate(-50%, -50%) translateX(0);
}

I'm doing this with a little bit of JavaScript in this demo: https://codepen.io/jh3y/pen/qBoOMVv/ffb50c595e43febebdf78555a88ead60

But, using show/hide doesn't achieve the effect desired. Currently, it only gets the desired effect if you use [togglepopup] element.

@flackr
Copy link
Author

flackr commented Jul 5, 2022

You should be able to set different animations using CSS animations, e.g.

[popup] {
  animation: exit-animation 250ms;
}

[popup]:top-layer {
  animation: entry-animation 250ms;
}

I agree that I think it can't be done with CSS transitions without either Javascript or adding additional pseudoclasses.

@css-meeting-bot
Copy link

The Open UI Community Group just discussed [Popup] Supporting transitions and animations on open and dismiss, and agreed to the following:

  • RESOLVED: The pop-up animation behavior will be the one described in this comment: https://github.com/openui/open-ui/issues/335#issuecomment-1168951980
The full IRC log of that discussion <gregwhitworth> Topic: [Popup] Supporting transitions and animations on open and dismiss
<gregwhitworth> github: https://github.com//issues/335
<JonathanNeal> masonf: This has to do with animations; how you can animate a popup in and out. We’ve been discussing behavior, and how you would handle or wait for animations. It’s described in much more detail in the issue. It’s currently implemented in the Chromium prototype.
<jhey> Only issue I had as mentioned in the issue was if you want to transition in from one side and out the other.
<JonathanNeal> masonf: It has to wait to check for any animations, but implementation-wise, it’s not too bad. I’m looking for general comments, but mainly if the proposal outlined would be agreed upon.
<flackr> q+
<JonathanNeal> jhey: say you wanted to do a scope transition, in from the left, then out to the right. You can’t do without some kind of JS intervention. You can’t tell if you are coming in or going out.
<gregwhitworth> ack flackr
<dbaron> q+ to ask about CSS transitions more generally
<JonathanNeal> flackr: I left a comment on the issue. This is possible with CSS Animations. You can set an animation on top layer that matches the opening popup, and another that matches the closing popup. It’s true that there’s nothing for transitions, but the animation strategy would work without JS.
<gregwhitworth> flackr comment is here: https://github.com//issues/335#issuecomment-1175367327
<JonathanNeal> flackr: We don’t have another good way to make animations only on opening/closing state.
<JonathanNeal> masonf: are there other cases in the platform where you can’t transition in different ways?
<gregwhitworth> q?
<JonathanNeal> flackr: it would be the case with anything that has two states that you would go between.
<JonathanNeal> masonf: like `:hover` coming in or going out.
<JonathanNeal> dbaron: Can you transition differently in and out of hover? You sort of can. It depends on what you mean. Maybe for the exact thing you can’t. You can certainly have a different value of the transition properties so that you get different transitions going in or out of hover, but it doesn’t help you if you want different values at the end of the transition.
<gregwhitworth> ack dbaron
<Zakim> dbaron, you wanted to ask about CSS transitions more generally
<JonathanNeal> dbaron: It feels a little bit like you are adding a mechanism that is useful that is adequate for one specific case. However, I’m not certain it extends very well into the other cases where people would want it.
<flackr> q+ to respond to dbaron
<JonathanNeal> masonf: here this is more nuanced because there is also the top layer. Generally, I try to avoid solving all the world’s problems in order to solve one. Maybe I’m imagining too much, but whatever solution would be general would be compatible with this.
<gregwhitworth> ack flackr
<Zakim> flackr, you wanted to respond to dbaron
<JonathanNeal> jhey: I understand there would be a lot of extra work to get directional transitions baked in.
<JonathanNeal> flackr: I think there might be a generic mechanism here. Possibly one that would even simplify what we’re doing with popup. I think there are two things. 1. If you have some sort of way to match against things that had just become visible, or no longer display none, then you could use that to set an initial state before transition. 2. On the outboard direction, if you had the ability to animate display to maintain the display not-none
<JonathanNeal> until the animation was finished, that would give you a way to do outbound animations. They are far more complicated in practice.
<JonathanNeal> flackr: currently, display is explicitly not animated. There is a good reason for this. There’s no reason animations couldn’t support other display values, but not in (CSS?) animations
<gregwhitworth> q?
<JonathanNeal> masonf: do yo think the current proposal is incompatible with this?
<JonathanNeal> flackr: it could be more simple. if we had a way to match, developers could use this other mechanism to start a transition.
<JonathanNeal> flackr: it could immediately match the top layer pseudo.
<JonathanNeal> flackr: the outbound direction would still need a carve-out, because it would need to remain in the top layer for targeting.
<JonathanNeal> masonf: not only does it need to be in the top layer, but it needs other things.
<JonathanNeal> flackr: think of it being like whether you’re allowed to have a non-display-none frame.
<JonathanNeal> masonf: the open question is: do we need to solve that difficult-sounding problem?
<JonathanNeal> dbaron: one thing that seems problematic about waiting for animations; if somebody has an animation on their popup that is continuous, like a spinner or something; then the popup would never go away.
<JonathanNeal> dbaron: I would never get display none.
<jhey> I think it's an interesting problem. It feels like the "transition" way kinda half solves it. Should it not solve it and people implement with JavaScript using "transitionend" listener? Or is there the inbound/outbound state added to :top-layer.
<JonathanNeal> masonf: correct
<flackr> https://github.com/w3c/csswg-drafts/issues/6429 is the issue about supporting animating display
<JonathanNeal> dbaron: it might be very confusing if somebody runs into it and they don’t know why their popup is still visible.
<JonathanNeal> masonf: that’s an excellent point. Developer Tools for this would be an excellent addition.
<jhey> Otherwise, this question might pop up often for how to inbound/outbound the easiest way. And before the transition piece, we could kinda get the current behavior.
<JonathanNeal> dbaron: I think the other option that might make it less problematic would be some combination of A. only consider animations that started after the transition started, or B. only consider animations with a finite duration.
<JonathanNeal> flackr: the former (A) is how it works
<JonathanNeal> dbaron: I was more worried about animations people put in their popup. That A is handled resolves that.
<gregwhitworth> q?
<flackr> or 1000 day transition
<Zakim> dbaron, you wanted to discuss one other bug
<JonathanNeal> dbaron: I might lean against doing B, knowing that A is handled.
<masonf> Proposed resolution: The pop-up animation behavior will be the one described in this comment: https://github.com//issues/335#issuecomment-1168951980
<JonathanNeal> +1
<AlexanderFutekov> +1
<flackr> +1
<dbaron> +1
<masonf> RESOLVED: The pop-up animation behavior will be the one described in this comment: https://github.com//issues/335#issuecomment-1168951980

@mfreed7
Copy link
Collaborator

mfreed7 commented Jul 7, 2022

Based on the resolution, I'll close this one. Thanks everyone!

@mfreed7 mfreed7 closed this as completed Jul 7, 2022
@bramus
Copy link

bramus commented Jul 7, 2022

Oh that’s nice! Tested in Canary (demo) and works like a charm!

On a sidenote: will a similar behavior be backported to <dialog> elements? Would love to have this. If wanted, at which venue should we pursue this?

@mfreed7
Copy link
Collaborator

mfreed7 commented Jul 8, 2022

Oh that’s nice! Tested in Canary (demo) and works like a charm!

On a sidenote: will a similar behavior be backported to <dialog> elements? Would love to have this. If wanted, at which venue should we pursue this?

Glad you like it! Side note: with today's Canary, you'll need to add top:0 to your CSS for the [popup]. I implemented the proposed standardized CSS from this issue yesterday. 😄

As for <dialog> I suppose it might be possible. It's an observable change though, so there might be compat issues.

@domenic
Copy link
Contributor

domenic commented Aug 19, 2022

+1 to considering backporting this to dialog, if someone is up for it!

@jods4
Copy link

jods4 commented Sep 28, 2022

Can someone confirm that this comment above matches the current spec (with show event being fired sync. last) and the open-ui.org website has not been updated yet (it says the show event is fired first):
#335 (comment)

I'm digging into this while evaluating if moving to popup for my currently JS-based elements would be feasible or not.
For context, most of those are animated (enter and exit) using Motion One, which behind the scene triggers Web Animations from JS.

I will take this opportunity to point out that although the entry animation seems to be doable using any means, the exit animation concept being built on top of getAnimations() is highly restrictive: most JS-based libraries can't be used as they often rely on requestAnimationFrame loops rather than Web Animation API -- Motion One being a novelty here.

@mfreed7
Copy link
Collaborator

mfreed7 commented Oct 3, 2022

Can someone confirm that this comment above matches the current spec (with show event being fired sync. last) and the open-ui.org website has not been updated yet (it says the show event is fired first): #335 (comment)

No, that bit changed to move the show event first:

#579 (comment)

The explainer and the spec PR should be up to date with the above comment. If not, please do comment back here. Does that behavior (firing show first) break something for you?

I will take this opportunity to point out that although the entry animation seems to be doable using any means, the exit animation concept being built on top of getAnimations() is highly restrictive: most JS-based libraries can't be used as they often rely on requestAnimationFrame loops rather than Web Animation API -- Motion One being a novelty here.

Thanks for the comment. I do agree that the API using getAnimations() precludes it working with pure-JS-driven animations. However, I don't think we could generally make this "wait" for rAF-driven animations, since there's no way to observe those. I think the idea is to push folks to use platform-based animations instead, and then this will play nicely. Perhaps a "no-op" animation can be added that runs for the same length of time as the JS-driven animation, so that the wait time is correct. Then the pop-up API will work correctly. Just a thought.

@flackr
Copy link
Author

flackr commented Oct 3, 2022

Perhaps a "no-op" animation can be added that runs for the same length of time as the JS-driven animation, so that the wait time is correct. Then the pop-up API will work correctly. Just a thought.

Absolutely, you can add an empty animation with the expected duration.

Also, in the future you could build your animation using something like the custom effect proposal from w3c/csswg-drafts#6861 (though we would need to ensure the custom effect is correctly targeted at an element within the popup).

@jods4
Copy link

jods4 commented Oct 3, 2022

Does that behavior (firing show first) break something for you?

Yes, it does: it means that when show is called, the popup element is not in its position. It might even still be display: none, so doesn't have a size.
If you want to start any animation that depends on final size and/or position you're out of luck. ☹️

I think the idea is to push folks to use platform-based animations instead, and then this will play nicely.

Currently, platform-based animations are lacking and are not always a substitute for JS-based solutions.

The effect proposal mentioned by @flackr looks like a good start to fill the gap. I am concerned by its current reliance on a fixed duration, though.
JS has to be used for physics-driven animations (e.g. springs) and effect being duration-based is not enabling them in the platform (although Matt Perry did amazing magic in Motion One to make this happen, I'm sure he'd love a less hacky solution).

Other use-cases for such a primitive can be found, for example if you want to orchestrate multiple animations inside the popup before closing the popup itself.

Given that an animation can be paused and completed from JS, I suppose creating a dummy empty animation is currently a work-around to animate a closing popup from JS.

When reading this new spec, I couldn't help but think that the platform should have a more general way to animate things that leave the DOM. This feels like a one-shot spec for popup, but all elements would benefit from the possibility of animating them when they're removed. I realize this is much more ambitious as the popup doesn't leave DOM but is simply removed from the top-layer and that solves many difficult layout questions.

@flackr
Copy link
Author

flackr commented Oct 4, 2022

The effect proposal mentioned by @flackr looks like a good start to fill the gap. I am concerned by its current reliance on a fixed duration, though.

A fixed duration isn't strictly required. You could for example create an infinite length animation and just remove the animation when you're done.

Other use-cases for such a primitive can be found, for example if you want to orchestrate multiple animations inside the popup before closing the popup itself.

This proposal does consider descendant animations, so as long as they are all initiated at the same time it will wait for the last one to finish. The shared element transitions API has a slightly more generous approach in that they will wait for any new animations even after previous ones finish.

When reading this new spec, I couldn't help but think that the platform should have a more general way to animate things that leave the DOM. This feels like a one-shot spec for popup, but all elements would benefit from the possibility of animating them when they're removed. I realize this is much more ambitious as the popup doesn't leave DOM but is simply removed from the top-layer and that solves many difficult layout questions.

I wondered this myself too, see #335 (comment) above for some thoughts there. The challenge is that with most elements the UA is not driving the process by which they become visible / go away - instead a style update or element removal triggers it.

mjfroman pushed a commit to mjfroman/moz-libwebrtc-third-party that referenced this issue Oct 14, 2022
Now, popups will follow this process when showing/hiding:

showPopup():
 1. Move the popup to the top layer, and remove the UA display:none
    style.
 2. Update style. (Transition initial style can be specified in this
    state.)
 3. Set the :top-layer pseudo class.
 4. Update style. (Animations/transitions happen here.)

hidePopup():
 1. Capture any already-running animations via getAnimations().
 2. Remove the :top-layer pseudo class.
 3. Update style. (Animations/transitions start here.)
 4. If the hidePopup() call is not due to a "force out" situation,
    getAnimations() again, remove any from step #1, and then wait here
    until all of them finish or are cancelled.
 4. Remove the popup from the top layer, and add the UA display:none
    style.
 5. Update style.

See this issue for more details:
  openui/open-ui#335

Bug: 1307772
Change-Id: Ia20eb6e9533c1a0b1029ca1279d42fe2648300af
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3688871
Reviewed-by: Robert Flack <flackr@chromium.org>
Commit-Queue: Mason Freed <masonf@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1014235}
NOKEYCHECK=True
GitOrigin-RevId: f659aa9aee9f710b49e821fff05896eba5aa0a5c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CSSWG Issues that have a relationship with CSS needs edits This is ready for edits to be made open-ui-resolved-accepted popover The Popover API
Projects
None yet
Development

No branches or pull requests

10 participants