diff --git a/files/en-us/web/api/cssstartingstylerule/index.md b/files/en-us/web/api/cssstartingstylerule/index.md new file mode 100644 index 000000000000000..ba8a7dc779da43a --- /dev/null +++ b/files/en-us/web/api/cssstartingstylerule/index.md @@ -0,0 +1,33 @@ +--- +title: CSSStartingStyleRule +slug: Web/API/CSSStartingStyleRule +page-type: web-api-interface +browser-compat: api.CSSStartingStyleRule +--- + +{{ APIRef("CSSOM") }} + +The **`CSSStartingStyleRule`** interface of the [CSS Object Model](/en-US/docs/Web/API/CSS_Object_Model) represents a CSS [`@starting-style`](/en-US/docs/Web/CSS/@starting-style) at-rule. + +{{InheritanceDiagram}} + +## Instance properties + +_This interface inherits properties from its parent, {{domxref("CSSGroupingRule")}}._ + +## Instance methods + +_This interface inherits methods from its parent, {{domxref("CSSGroupingRule")}}._ + +## Specifications + +{{Specifications}} + +## Browser compatibility + +{{Compat}} + +## See also + +- [`@starting-style`](/en-US/docs/Web/CSS/@starting-style) +- [Using dynamic styling information](/en-US/docs/Web/API/CSS_Object_Model/Using_dynamic_styling_information) diff --git a/files/en-us/web/api/popover_api/using/index.md b/files/en-us/web/api/popover_api/using/index.md index 7d5d3d0a33e1fff..e7de99a2b0c60cb 100644 --- a/files/en-us/web/api/popover_api/using/index.md +++ b/files/en-us/web/api/popover_api/using/index.md @@ -301,4 +301,196 @@ The {{cssxref("::backdrop")}} pseudo-element is a full-screen element placed dir See our [Popover blur background example](https://mdn.github.io/dom-examples/popover-api/blur-background/) ([source](https://github.com/mdn/dom-examples/tree/main/popover-api/blur-background)) for an idea of how this renders. -Finally, animation needs a special mention, as a lot of people are going to want to animate popovers between showing and hiding. As it stands, [a few updates to CSS behavior](https://open-ui.org/components/popover.research.explainer/#animation-of-popovers) are required to get popovers to be animatable, most notably enabling animation of elements as they move to and from `display: none`. We'll update this article as soon as animation is available for popovers. +## Animating popovers + +Popovers are set to `display: none;` when hidden and `display: block;` when shown, as well as being removed from / added to the {{glossary("top layer")}}. Therefore, for popovers to be animated `display` needs to be animatable. This is now the case; [supporting browsers](/en-US/docs/Web/CSS/display#browser_compatibility) animate `display` with a variation on the [discrete animation type](/en-US/docs/Web/CSS/CSS_animated_properties#discrete). Specifically, the browser will flip between `none` and another value of `display` so that the animated content is shown for `100%` of the animation duration. So for example: + +- When animating between `display` `none` and `block`, the value will flip to `block` at `0%` of the animation duration so it is visible throughout. +- When animating between `display` `block` and `none`, the value will flip to `none` at `100%` of the animation duration so it is visible throughout. + +### Transitioning a popover + +When animating popovers with transitions, the following are required: + +- [`@starting-style`](/en-US/docs/Web/CSS/@starting-style) is used to provide a set of starting values for properties set on the popover that you want to transition from when it is shown. This is needed because, by default, [CSS transitions](/en-US/docs/Web/CSS/CSS_transitions) are not triggered on elements' first style updates, or when the `display` type changes from `none` to another type, to avoid unexpected behavior. +- [`display`](/en-US/docs/Web/CSS/display) needs to be added to the transitions list so that the popover will remain as `display: block` for the duration of the animation, allowing the other animations to be seen. +- [`overlay`](/en-US/docs/Web/CSS/overlay) needs to be added to the transitions list so that the removal of the popover from the top layer will be deferred until the animation is finished, again allowing the other animations to be seen. +- [`transition-behavior: allow-discrete`](/en-US/docs/Web/CSS/transition-behavior) needs to be set on the `display` and `overlay` transitions. This effectively enables discrete transitions. + +Let's have a look at an example so you can see what this looks like: + +The HTML contains a {{htmlelement("div")}} element declared as a popover, and a {{htmlelement("button")}} element designated as the popover's toggle control: + +```html + +
I'm a Popover! I should animate.
+``` + +The CSS for the example looks like this: + +```css +html { + font-family: Arial, Helvetica, sans-serif; +} + +/* Transition for the popover itself */ + +[popover]:popover-open { + opacity: 1; + transform: scaleX(1); +} + +[popover] { + font-size: 1.2rem; + padding: 10px; + opacity: 0; + transform: scaleX(0); + transition: + opacity 0.7s, + transform 0.7s, + overlay 0.7s allow-discrete, + display 0.7s allow-discrete; +} + +/* Needs to be after the previous [popover]:popover-open rule to take effect, + as the specificity is the same */ +@starting-style { + [popover]:popover-open { + opacity: 0; + transform: scaleX(0); + } +} + +/* Transition for the popover's backdrop */ + +[popover]::backdrop { + background-color: rgba(0, 0, 0, 0); + transition: + display 0.7s allow-discrete, + overlay 0.7s allow-discrete, + background-color 0.7s; +} + +[popover]:popover-open::backdrop { + background-color: rgba(0, 0, 0, 0.25); +} + +/* This starting-style rule cannot be nested inside the above selector +because the nesting selector cannot represent pseudo-elements. */ + +@starting-style { + [popover]:popover-open::backdrop { + background-color: rgba(0, 0, 0, 0); + } +} +``` + +The two popover properties we want to show animations for are [`opacity`](/en-US/docs/Web/CSS/opacity) and [`transform`](/en-US/docs/Web/CSS/transform) — specifically, a horizontally scaling transform. We want the popover to fade in and out, as well as growing/shrinking horizontally. To achieve this, we have set a starting state for these properties on the default hidden state of the popover element (selected via `[popover]`), and an end state on the open state of the popover (selected via the [`:popover-open`](/en-US/docs/Web/CSS/:popover-open) pseudo-class). We then set a [`transition`](/en-US/docs/Web/CSS/transition) property to animate between the two. + +And as discussed earlier, we have: + +- Set a starting state for the transition inside the `@starting-style` block. +- Added `display` to the list of transitioned elements so that the animated element is visible (set to `display: block`) throughout both the entry and exit animation. Without this, the exit animation would not be visible; in effect, the popover would just disappear. +- Added `overlay` to the list of transitioned elements to make sure that the removal of the element from the top layer is deferred until the animation has been completed. This doesn't make a huge difference for simple animations such as this one, but in more complex cases not doing this can result in the element being removed from the overlay too quickly, meaning the animation is not smooth or effective. +- Set `allow-discrete` on both the above transitions to enable discrete transitions. + +You'll note that we've also included a transition on the [`::backdrop`](/en-US/docs/Web/CSS/::backdrop) that appears behind the popover when it opens, to provide a nice darkening animation. `[popover]:popover-open::backdrop` is needed to select the backdrop when the popover is open. + +The code renders as follows: + +{{ EmbedLiveSample("Transitioning a popover", "100%", "200") }} + +### A popover keyframe animation + +When animating a popover with [CSS animations](/en-US/docs/Web/CSS/CSS_animations), there are some differences to note: + +- You don't provide a starting state inside a `@starting-style` block; instead, you need to provide the starting `display` value in an explicit starting keyframe (for example using `0%` or `from`). +- You don't need to enable discrete transitions; there is no equivalent to `allow-discrete` inside keyframes. +- You can't set `overlay` inside keyframes either. The best solution we found at this time to defer removal from the top layer was to use JavaScript — add/remove classes to animate the popover as required and then use {{domxref("setTimeout()")}} to defer hiding the popover until after the animation is finished. + +Let's look at an example. The HTML contains a {{htmlelement("div")}} element declared as a popover, and a {{htmlelement("button")}} element designated as the popover's toggle control: + +```html + +
I'm a Popover! I should animate.
+``` + +The CSS is as follows: + +```css +html { + font-family: Arial, Helvetica, sans-serif; +} + +[popover] { + font-size: 1.2rem; + padding: 10px; +} + +/* Animation classes */ + +.fade-in { + animation: fade-in 0.7s ease-out forwards; +} + +.fade-out { + animation: fade-out 0.7s ease-out forwards; +} + +/* Animation keyframes */ + +@keyframes fade-in { + 0% { + opacity: 0; + transform: scaleX(0); + display: none; + } + + 100% { + opacity: 1; + transform: scaleX(1); + display: block; + } +} + +@keyframes fade-out { + 0% { + opacity: 1; + transform: scaleX(1); + display: block; + } + + 100% { + opacity: 0; + transform: scaleX(0); + display: none; + } +} +``` + +We have defined keyframes that specify the desired entry and exit animations, and assigned those to CSS classes. This example doesn't animate the backdrop like the transitions example above does — that wasn't possible to reproduce with a keyframe animation at the time of writing. + +We then use some rudimentary JavaScript to apply those classes to the popover as it is toggled between shown and hidden (via the handy {{domxref("HTMLElement.beforetoggle_event", "beforetoggle")}} event), triggering the animations at the right times. As mentioned earlier, we have used `setTimeout()` to defer hiding the popover until the animations have finished. + +```js +const popover = document.getElementById("mypopover"); +const toggleBtn = document.getElementById("toggle-button"); + +popover.addEventListener("beforetoggle", () => { + if (!popover.matches(":popover-open")) { + popover.classList.remove("fade-out"); + popover.classList.add("fade-in"); + popover.showPopover(); + } else if (popover.matches(":popover-open")) { + popover.classList.remove("fade-in"); + popover.classList.add("fade-out"); + setTimeout(() => { + popover.hidePopover(); + }, 700); + } +}); +``` + +The code renders as follows: + +{{ EmbedLiveSample("A popover keyframe animation", "100%", "200") }} diff --git a/files/en-us/web/css/@starting-style/index.md b/files/en-us/web/css/@starting-style/index.md new file mode 100644 index 000000000000000..4ba85e730fed78f --- /dev/null +++ b/files/en-us/web/css/@starting-style/index.md @@ -0,0 +1,310 @@ +--- +title: "@starting-style" +slug: Web/CSS/@starting-style +page-type: css-at-rule +status: + - experimental +browser-compat: css.at-rules.starting-style +--- + +{{CSSRef}}{{SeeCompatTable}} + +The **`@starting-style`** [CSS](/en-US/docs/Web/CSS) [at-rule](/en-US/docs/Web/CSS/At-rule) is used to provide a set of starting values for properties set on an element that you want to transition from when the element receives its first style update. + +This is needed because, by default, [CSS transitions](/en-US/docs/Web/CSS/CSS_transitions) are not triggered on elements' first style updates, or when the [`display`](/en-US/docs/Web/CSS/display) type changes from `none` to another type, to avoid unexpected behavior. To put it another way, such elements do not have a starting style to transition from. `@starting-style` allows you to provide starting styles, overriding the default behavior in a specific controlled fashion. + +This enables the easy creation of entry animations that were previously complex to achieve, such as animating elements when they are changed from `display: none` (this includes elements shown in the [top layer](/en-US/docs/Glossary/Top_layer) such as [popovers](/en-US/docs/Web/API/Popover_API) or modal {{htmlelement("dialog")}} elements) or when they are first added to the DOM. + +> **Note:** `@starting-style` is only relevant to CSS transitions. When animating from `display: none` or animating elements as they are first added to the DOM, [CSS animations](/en-US/docs/Web/CSS/CSS_animations) do not need a `@starting-style` specified; instead you provide the starting style as an explicit starting keyframe (for example using `0%` or `from`). See [Using CSS animations](/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations) for an example. + +## Syntax + +```css +@starting-style {rules} +@starting-style {declarations} +``` + +where: + +- _rules_ + - : Is the set of CSS rules providing the starting styles for the transition, when `@starting-style` is used in standalone style. +- _rules_ + - : Is the set of CSS declarations providing the starting styles for the transition, when `@starting-style` is nested inside a particular ruleset. + +## Description + +There are two ways to use `@starting-style`. Let's consider an example where we want to animate a popover when it is shown (added to the top layer). In this case, the original rule specifying the styles for the popover once opened looks like this (you can see the [full example in action](/en-US/docs/Web/CSS/@starting-style#animating_a_popover) in the examples section): + +```css +[popover]:popover-open { + opacity: 1; + transform: scaleX(1); +} +``` + +You can specify the starting styles in a separate rule contained within a standalone `@starting-style` block: + +```css +@starting-style { + [popover]:popover-open { + opacity: 0; + transform: scaleX(0); + } +} +``` + +> **Note:** In the standalone case, you need to specify the `@starting-style` block after the original rule for it to take effect, as the specificity of each is the same. If `@starting-style` was specified first, the original styles would override it. + +Alternatively, you can nest the starting styles inside the original rule: + +```css +[popover]:popover-open { + opacity: 1; + transform: scaleX(1); + + @starting-style { + opacity: 0; + transform: scaleX(0); + } +} +``` + +## Formal syntax + +{{csssyntax}} + +## Examples + +### Animating a popover + +This example shows how a [popover](/en-US/docs/Web/API/Popover_API) can be animated using [CSS transitions](/en-US/docs/Web/CSS/CSS_transitions). Basic entry and exit animations are provided. + +The HTML contains a {{htmlelement("div")}} element declared as a popover using the [popover](/en-US/docs/Web/HTML/Global_attributes/popover) attribute, and a {{htmlelement("button")}} element designated as the popover's toggle control using its [popovertarget](/en-US/docs/Web/HTML/Element/button#popovertarget) attribute. + +```html + +
I'm a Popover! I should animate.
+``` + +The CSS for the example looks like this: + +```css +html { + font-family: Arial, Helvetica, sans-serif; +} + +[popover]:popover-open { + opacity: 1; + transform: scaleX(1); +} + +[popover] { + font-size: 1.2rem; + padding: 10px; + opacity: 0; + transform: scaleX(0); + transition: + opacity 0.7s, + transform 0.7s, + overlay 0.7s allow-discrete, + display 0.7s allow-discrete; +} + +/* Needs to be included after the previous [popover]:popover-open rule + to take effect, as the specificity is the same */ +@starting-style { + [popover]:popover-open { + opacity: 0; + transform: scaleX(0); + } +} + +/* Transition for the popover's backdrop */ + +[popover]::backdrop { + background-color: rgba(0, 0, 0, 0); + transition: + display 0.7s allow-discrete, + overlay 0.7s allow-discrete, + background-color 0.7s; +} + +[popover]:popover-open::backdrop { + background-color: rgba(0, 0, 0, 0.25); +} + +/* This starting-style rule cannot be nested inside the above selector +because the nesting selector cannot represent pseudo-elements. */ + +@starting-style { + [popover]:popover-open::backdrop { + background-color: rgba(0, 0, 0, 0); + } +} +``` + +The two properties we want to animate are [`opacity`](/en-US/docs/Web/CSS/opacity) and [`transform`](/en-US/docs/Web/CSS/transform) (specifically, a horizontally scaling transform): we want the popover to fade in and out, as well as growing/shrinking horizontally. To achieve this, we have set a starting state for these properties on the default hidden state of the popover element (selected via `[popover]`), and an end state on the open state of the popover (selected via the [`:popover-open`](/en-US/docs/Web/CSS/:popover-open) pseudo-class). + +We then set a [`transition`](/en-US/docs/Web/CSS/transition) property to animate between the two, and include a starting state for the animation inside a [`@starting-style`](/en-US/docs/Web/CSS/@starting-style) at-rule, as described above, so the entry animation will work. + +However, because the animated element is being promoted to the [top layer](/en-US/docs/Glossary/Top_layer) when shown and removed from the top layer when hidden — which also means that its hidden state has [`display: none`](/en-US/docs/Web/CSS/display) set on it — some extra steps are required to get the animation working in both directions: + +- `display` is added to the list of transitioned elements so that the animated element is visible (set to `display: block`) throughout both the entry and exit animation. Without this, the exit animation would not be visible; in effect, the popover would just disappear. Note that the [`transition-behavior: allow-discrete`](/en-US/docs/Web/CSS/transition-behavior) value is also set in the shorthand so that it will animate. +- [`overlay`](/en-US/docs/Web/CSS/overlay) is added to the list of transitioned elements to make sure that the removal of the element from the top layer is deferred until the animation has been completed. This doesn't make a huge difference for simple animations such as this one, but in more complex cases not doing this can result in the element being removed from the overlay too quickly, meaning the animation is not smooth or effective. Again, `transition-behavior: allow-discrete` is required in this case for the animation to occur. + +You'll note that we've also included a transition on the [`::backdrop`](/en-US/docs/Web/CSS/::backdrop) that appears behind the popover when it opens, to provide a nice darkening animation. `[popover]:popover-open::backdrop` is needed to select the backdrop when the popover is open. + +The code renders as follows: + +{{ EmbedLiveSample("Animating a popover", "100%", "200") }} + +### Transitioning elements as they are added to and removed from the DOM + +In this example, we provide a button that appends new elements to a {{htmlelement("section")}} container when pressed. Each element is given a nested close button which, when pressed, removes the element again. We use transitions to animate the elements when they are first added to the DOM, and when they are removed. + +The static HTML looks like this: + +```html + +
+``` + +The JavaScript that handles the adding and removing looks like this: + +```js +const btn = document.querySelector("button"); +const sectionElem = document.querySelector("section"); + +btn.addEventListener("click", createColumn); + +function randomColor() { + function randomChannel() { + return Math.floor(Math.random() * 255); + } + + return `rgb(${randomChannel()},${randomChannel()},${randomChannel()})`; +} + +function createColumn() { + const divElem = document.createElement("div"); + divElem.style.backgroundColor = randomColor(); + + const closeBtn = document.createElement("button"); + closeBtn.textContent = "✖"; + closeBtn.setAttribute("aria-label", "close"); + divElem.append(closeBtn); + sectionElem.append(divElem); + + closeBtn.addEventListener("click", () => { + divElem.classList.add("fade-out"); + + setTimeout(() => { + divElem.remove(); + }, 1000); + }); +} +``` + +The most interesting part is the `createColumn()` function — note how it creates a {{htmlelement("div")}} element and a {{htmlelement("button")}} element to close the `
` when pressed then appends the ` +
I'm a Popover! I should animate.
+``` + +The CSS for the example looks like this: + +```css +html { + font-family: Arial, Helvetica, sans-serif; +} + +[popover]:popover-open { + opacity: 1; + transform: scaleX(1); +} + +[popover] { + font-size: 1.2rem; + padding: 10px; + opacity: 0; + transform: scaleX(0); + transition: + opacity 0.7s, + transform 0.7s, + overlay 0.7s allow-discrete, + display 0.7s allow-discrete; +} + +/* Needs to be included after the previous [popover]:popover-open rule + to take effect, as the specificity is the same */ +@starting-style { + [popover]:popover-open { + opacity: 0; + transform: scaleX(0); + } +} + +/* Transition for the popover's backdrop */ + +[popover]::backdrop { + background-color: rgba(0, 0, 0, 0); + transition: + display 0.7s allow-discrete, + overlay 0.7s allow-discrete, + background-color 0.7s; +} + +[popover]:popover-open::backdrop { + background-color: rgba(0, 0, 0, 0.25); +} + +/* This starting-style rule cannot be nested inside the above selector +because the nesting selector cannot represent pseudo-elements. */ + +@starting-style { + [popover]:popover-open::backdrop { + background-color: rgba(0, 0, 0, 0); + } +} +``` + +The two properties we want to animate are [`opacity`](/en-US/docs/Web/CSS/opacity) and [`transform`](/en-US/docs/Web/CSS/transform) (specifically, a horizontally scaling transform): we want the popover to fade in and out, as well as growing/shrinking horizontally. To achieve this, we have set a starting state for these properties on the default hidden state of the popover element (selected via `[popover]`), and an end state on the open state of the popover (selected via the [`:popover-open`](/en-US/docs/Web/CSS/:popover-open) pseudo-class). We then set a [`transition`](/en-US/docs/Web/CSS/transition) property to animate between the two. + +However, because the animated element is being promoted to the [top layer](/en-US/docs/Glossary/Top_layer) when shown and removed from the top layer when hidden — which also means that its hidden state has [`display: none`](/en-US/docs/Web/CSS/display) set on it — some extra steps are required to get the animation working in both directions: + +- A starting state for the animation is set inside the [`@starting-style`](/en-US/docs/Web/CSS/@starting-style) at-rule. This is needed because by default transitions are not triggered on elements' first style updates, or when the `display` type changes from `none` to another type, to avoid unexpected behavior. `@starting-style` allows you to override that default in a specific controlled fashion. Without this, the entry animation would not occur and the popover would just appear. +- `display` is added to the list of transitioned elements so that the animated element is visible (set to `display: block`) throughout both the entry and exit animation. Without this, the exit animation would not be visible; in effect, the popover would just disappear. Note that the [`transition-behavior: allow-discrete`](/en-US/docs/Web/CSS/transition-behavior) value is also set in the shorthand so that it will animate. +- `overlay` is added to the list of transitioned elements to make sure that the removal of the element from the top layer is deferred until the animation has been completed. This doesn't make a huge difference for simple animations such as this one, but in more complex cases not doing this can result in the element being removed from the overlay too quickly, meaning the animation is not smooth or effective. Again, `transition-behavior: allow-discrete` is required in this case for the animation to occur. + +You'll note that we've also included a transition on the [`::backdrop`](/en-US/docs/Web/CSS/::backdrop) that appears behind the popover when it opens, to provide a nice darkening animation. `[popover]:popover-open::backdrop` is needed to select the backdrop when the popover is open. + +The code renders as follows: + +{{ EmbedLiveSample("Animating a popover", "100%", "200") }} + +#### Further examples + +Further examples (including a `` modal animation) can be found at [Animating elements to and from the top-layer](https://developer.chrome.com/blog/entry-exit-animations/#animating-elements-to-and-from-the-top-layer). + +## Specifications + +{{Specifications}} + +## Browser compatibility + +{{Compat}} + +## See also + +- [`@starting-style`](/en-US/docs/Web/CSS/@starting-style) +- [`transition-behavior`](/en-US/docs/Web/CSS/transition-behavior) +- [Four new CSS features for smooth entry and exit animations](https://developer.chrome.com/blog/entry-exit-animations/) on developer.chrome.com (2023) diff --git a/files/en-us/web/css/transition-behavior/index.md b/files/en-us/web/css/transition-behavior/index.md new file mode 100644 index 000000000000000..73e333ed582f26a --- /dev/null +++ b/files/en-us/web/css/transition-behavior/index.md @@ -0,0 +1,162 @@ +--- +title: transition-behavior +slug: Web/CSS/transition-behavior +page-type: css-property +status: + - experimental +browser-compat: css.properties.transition-behavior +--- + +{{CSSRef}}{{SeeCompatTable}} + +The **`transition-behavior`** [CSS](/en-US/docs/Web/CSS) property specifies whether transitions will be started for properties whose animation behavior is [discrete](/en-US/docs/Web/CSS/CSS_animated_properties#discrete). + +This is most significant in the cases of [`display`](/en-US/docs/Web/CSS/display), [`content-visibility`](/en-US/docs/Web/CSS/display), and [`overlay`](/en-US/docs/Web/CSS/overlay), the first two of which historically were not animatable. The ability of these elements to be transitioned means that it is fairly easy to create entry and exit animations, where an element is transitioned to and from a hidden state (which includes elements appearing in the [top layer](/en-US/docs/Glossary/Top_layer) such as [popovers](/en-US/docs/Web/API/Popover_API) or modal {{htmlelement("dialog")}} elements), or transitioned as soon as it is added to the DOM. + +## Syntax + +```css +/* Keyword values */ +transition-behavior: allow-discrete; +transition-behavior: normal; + +/* Global values */ +transition-behavior: inherit; +transition-behavior: initial; +transition-behavior: revert; +transition-behavior: revert-layer; +transition-behavior: unset; +``` + +### Values + +- `allow-discrete` + - : Transitions will be started on the element for discrete animated properties. +- `normal` + - : Transitions will _not_ be started on the element for discrete animated properties. + +## Formal definition + +{{cssinfo}} + +## Formal syntax + +{{CSSSyntax}} + +## Examples + +### Basic usage + +```css +.card { + transition-property: opacity, display; + transition-duration: 0.25s, 0.25s; + transition-behavior: allow-discrete; +} + +.card.fade-out { + opacity: 0; + display: none; +} +``` + +See the next example for shorthand usage. + +### Animating a popover + +This example shows how a [popover](/en-US/docs/Web/API/Popover_API) can be animated using [CSS transitions](/en-US/docs/Web/CSS/CSS_transitions). Basic entry and exit animations are provided. + +The HTML contains a {{htmlelement("div")}} element declared as a popover using the [popover](/en-US/docs/Web/HTML/Global_attributes/popover) attribute, and a {{htmlelement("button")}} element designated as the popover's toggle control using its [popovertarget](/en-US/docs/Web/HTML/Element/button#popovertarget) attribute. + +```html + +
I'm a Popover! I should animate.
+``` + +The CSS for the example looks like this: + +```css +html { + font-family: Arial, Helvetica, sans-serif; +} + +[popover]:popover-open { + opacity: 1; + transform: scaleX(1); +} + +[popover] { + font-size: 1.2rem; + padding: 10px; + opacity: 0; + transform: scaleX(0); + transition: + opacity 0.7s, + transform 0.7s, + overlay 0.7s allow-discrete, + display 0.7s allow-discrete; +} + +/* Needs to be included after the previous [popover]:popover-open rule + to take effect, as the specificity is the same */ +@starting-style { + [popover]:popover-open { + opacity: 0; + transform: scaleX(0); + } +} + +/* Transition for the popover's backdrop */ + +[popover]::backdrop { + background-color: rgba(0, 0, 0, 0); + transition: + display 0.7s allow-discrete, + overlay 0.7s allow-discrete, + background-color 0.7s; +} + +[popover]:popover-open::backdrop { + background-color: rgba(0, 0, 0, 0.25); +} + +/* This starting-style rule cannot be nested inside the above selector +because the nesting selector cannot represent pseudo-elements. */ + +@starting-style { + [popover]:popover-open::backdrop { + background-color: rgba(0, 0, 0, 0); + } +} +``` + +The two properties we want to animate are [`opacity`](/en-US/docs/Web/CSS/opacity) and [`transform`](/en-US/docs/Web/CSS/transform) (specifically, a horizontally scaling transform): we want the popover to fade in and out, as well as growing/shrinking horizontally. To achieve this, we have set a starting state for these properties on the default hidden state of the popover element (selected via `[popover]`), and an end state on the open state of the popover (selected via the [`:popover-open`](/en-US/docs/Web/CSS/:popover-open) pseudo-class). We then set a [`transition`](/en-US/docs/Web/CSS/transition) property to animate between the two. + +However, because the animated element is being promoted to the [top layer](/en-US/docs/Glossary/Top_layer) when shown and removed from the top layer when hidden — which also means that its hidden state has [`display: none`](/en-US/docs/Web/CSS/display) set on it — some extra steps are required to get the animation working in both directions: + +- A starting state for the animation is set inside the [`@starting-style`](/en-US/docs/Web/CSS/@starting-style) at-rule. This is needed because by default transitions are not triggered on elements' first style updates, or when the `display` type changes from `none` to another type, to avoid unexpected behavior. `@starting-style` allows you to override that default in a specific controlled fashion. Without this, the entry animation would not occur and the popover would just appear. +- `display` is added to the list of transitioned elements so that the animated element is visible (set to `display: block`) throughout both the entry and exit animation. Without this, the exit animation would not be visible; in effect, the popover would just disappear. +- [`overlay`](/en-US/docs/Web/CSS/overlay) is added to the list of transitioned elements to make sure that the removal of the element from the top layer is deferred until the animation has been completed. This doesn't make a huge difference for simple animations such as this one, but in more complex cases not doing this can result in the element being removed from the overlay too quickly, meaning the animation is not smooth or effective. + +In the transitions list, `transition-behavior: allow-discrete` is set in the shorthand for both `display` and `overlay` so that they will animate. + +You'll note that we've also included a transition on the [`::backdrop`](/en-US/docs/Web/CSS/::backdrop) that appears behind the popover when it opens, to provide a nice darkening animation. `[popover]:popover-open::backdrop` is needed to select the backdrop when the popover is open. + +The code renders as follows: + +{{ EmbedLiveSample("Animating a popover", "100%", "200") }} + +## Specifications + +{{Specifications}} + +## Browser compatibility + +{{Compat}} + +## See also + +- [`overlay`](/en-US/docs/Web/CSS/overlay) +- [`@starting-style`](/en-US/docs/Web/CSS/@starting-style) +- [`transition`](/en-US/docs/Web/CSS/transition) +- [Four new CSS features for smooth entry and exit animations](https://developer.chrome.com/blog/entry-exit-animations/) on developer.chrome.com (2023) diff --git a/files/en-us/web/css/transition-property/index.md b/files/en-us/web/css/transition-property/index.md index 2d21e5c3b12b1e3..8169c4e5cd2e935 100644 --- a/files/en-us/web/css/transition-property/index.md +++ b/files/en-us/web/css/transition-property/index.md @@ -60,33 +60,34 @@ transition-property: unset; ## Examples -### Simple example +> **Note:** See our [Using CSS transitions](/en-US/docs/Web/CSS/CSS_transitions/Using_CSS_transitions) guide for more `transition-property` examples. -This example performs a four-second font size transition when the user hovers over the element, the `transition-property` is the `font-size`. +### Basic example -#### HTML +When the button is hovered or focused, it undergoes a one-second opacity transition; the `transition-property` is [`opacity`](/en-US/docs/Web/CSS/opacity). + +The HTML looks like this: ```html -Hover over me + ``` -#### CSS +The CSS is as follows: ```css .target { - font-size: 14px; - transition-property: font-size; - transition-duration: 4s; + font-size: 20px; + transition-property: opacity; + transition-duration: 1s; } -.target:hover { - font-size: 36px; +.target:hover, +.target:focus { + opacity: 0; } ``` -{{EmbedLiveSample('Simple_example', 600, 100)}} - -You will find more examples of `transition-property` included in the main [CSS transitions](/en-US/docs/Web/CSS/CSS_transitions/Using_CSS_transitions) article. +{{EmbedLiveSample('Basic_example', 600, 100)}} ## Specifications diff --git a/files/en-us/web/css/transition/index.md b/files/en-us/web/css/transition/index.md index 636c9530b8921cd..27be632bb1e5f64 100644 --- a/files/en-us/web/css/transition/index.md +++ b/files/en-us/web/css/transition/index.md @@ -7,7 +7,7 @@ browser-compat: css.properties.transition {{CSSRef}} -The **`transition`** [CSS](/en-US/docs/Web/CSS) property is a [shorthand property](/en-US/docs/Web/CSS/Shorthand_properties) for {{ cssxref("transition-property") }}, {{ cssxref("transition-duration") }}, {{ cssxref("transition-timing-function") }}, and {{ cssxref("transition-delay") }}. +The **`transition`** [CSS](/en-US/docs/Web/CSS) property is a [shorthand property](/en-US/docs/Web/CSS/Shorthand_properties) for {{ cssxref("transition-property") }}, {{ cssxref("transition-duration") }}, {{ cssxref("transition-timing-function") }}, {{ cssxref("transition-delay") }}, and {{ cssxref("transition-behavior") }}. {{EmbedInteractiveExample("pages/css/transition.html")}} @@ -17,6 +17,7 @@ Transitions enable you to define the transition between two states of an element This property is a shorthand for the following CSS properties: +- [`transition-behavior`](/en-US/docs/Web/CSS/transition-behavior) {{experimental_inline}} - [`transition-delay`](/en-US/docs/Web/CSS/transition-delay) - [`transition-duration`](/en-US/docs/Web/CSS/transition-duration) - [`transition-property`](/en-US/docs/Web/CSS/transition-property) @@ -38,6 +39,9 @@ transition: margin-right 4s ease-in-out; /* property name | duration | easing function | delay */ transition: margin-right 4s ease-in-out 1s; +/* property name | duration | behavior */ +transition: display 4s allow-discrete; + /* Apply to 2 properties */ transition: margin-right 4s, @@ -66,6 +70,10 @@ Each single-property transition describes the transition that should be applied - zero or one {{cssxref("<easing-function>")}} value representing the easing function to use - zero, one, or two {{cssxref("<time>")}} values. The first value that can be parsed as a time is assigned to the {{cssxref("transition-duration")}}, and the second value that can be parsed as a time is assigned to {{cssxref("transition-delay")}}. +- zero or one value declaring whether to start transitions for properties whose animation behavior is [discrete](/en-US/docs/Web/CSS/CSS_animated_properties#discrete): + + - the keyword `allow-discrete` + - the keyword `normal` See [how things are handled](/en-US/docs/Web/CSS/CSS_transitions/Using_CSS_transitions#when_property_value_lists_are_of_different_lengths) when lists of property values aren't the same length. In short, extra transition descriptions beyond the number of properties actually being animated are ignored. @@ -79,7 +87,9 @@ See [how things are handled](/en-US/docs/Web/CSS/CSS_transitions/Using_CSS_trans ## Examples -### Simple example +> **Note:** See our [Using CSS transitions](/en-US/docs/Web/CSS/CSS_transitions/Using_CSS_transitions) guide for more `transition` examples. + +### Basic example In this example, when the user hovers over the element, there is a one-second delay before the four-second `font-size` transition occurs. @@ -104,9 +114,29 @@ We include two {{cssxref("time")}} values. In the `transition` shorthand, the fi } ``` -{{EmbedLiveSample('Simple_example', 600, 100)}} +{{EmbedLiveSample('Basic_example', 600, 100)}} + +### Transitioning top layer elements + +The [`overlay`](/en-US/docs/Web/CSS/overlay) property can be added to the list of transition properties set on an element displayed in the [top layer](/en-US/docs/Glossary/Top_layer) — for example a [popover](/en-US/docs/Web/API/Popover_API) or modal {{htmlelement("dialog")}} element. This causes its removal from the top layer to be deferred so it can be animated instead of disappearing immediately. + +Sample CSS: + +```css +[popover] { + font-size: 1.2rem; + padding: 10px; + opacity: 0; + transform: scaleX(0); + transition: + opacity 0.7s, + transform 0.7s, + overlay 0.7s allow-discrete, + display 0.7s allow-discrete; +} +``` -There are several more examples of CSS transitions included in the [Using CSS transitions](/en-US/docs/Web/CSS/CSS_transitions/Using_CSS_transitions) article. +See the [`overlay`](/en-US/docs/Web/CSS/overlay) page for a full working example. ## Specifications diff --git a/files/en-us/web/html/element/dialog/index.md b/files/en-us/web/html/element/dialog/index.md index ce0cde43a27726d..a017dba3fc80e23 100644 --- a/files/en-us/web/html/element/dialog/index.md +++ b/files/en-us/web/html/element/dialog/index.md @@ -34,7 +34,7 @@ This element includes the [global attributes](/en-US/docs/Web/HTML/Global_attrib ## Examples -### Caveats of creating a dialog using only HTML +### HTML-only dialog This example demonstrates the create a non-modal dialog by using only HTML. Because of the boolean `open` attribute in the `` element, the dialog appears open when the page loads. The dialog can be closed by clicking the "OK" button because the `method` attribute in the `
` element is set to `"dialog"`. In this case, no JavaScript is needed to close the form. @@ -49,7 +49,7 @@ This example demonstrates the create a non-modal dialog by using only HTML. Beca #### Result -{{EmbedLiveSample("Caveats_of_creating_a_dialog_using_only_HTML", "100%", 200)}} +{{EmbedLiveSample("HTML-only_dialog", "100%", 200)}} This dialog is initially open because of the presence of the `open` attribute. Dialogs that are displayed using the `open` attribute are non-modal. You may notice that after clicking "OK", the dialog gets dismissed leaving the Result frame empty. When the dialog is dismissed, there is no method provided to reopen it. For this reason, the preferred method to display non-modal dialogs is by using the {{domxref("HTMLDialogElement.show()")}} method. It is possible to toggle the display of the dialog by adding or removing the boolean `open` attribute, but it is not the recommended practice. @@ -108,7 +108,7 @@ closeButton.addEventListener("click", () => { #### Result -{{EmbedLiveSample("Modal_example", "100%", 200)}} +{{EmbedLiveSample("Creating_a_modal_dialog", "100%", 200)}} When the modal dialog is displayed, it appears above any other dialogs that might be present. Everything outside the modal dialog is inert and interactions outside the dialog are blocked. Notice that when the dialog is open, with the exception of the dialog itself, interaction with the document is not possible; the "Show the dialog" button is mostly obfuscated by the almost opaque backdrop of the dialog and is inert. @@ -200,6 +200,211 @@ Without an `action`, submitting the form via the default {{HTTPMethod("GET")}} m It is important to provide a closing mechanism within every `dialog` element. The Esc key does not close non-modal dialogs by default, nor can one assume that a user will even have access to a physical keyboard (e.g., someone using a touch screen device without access to a keyboard). +### Animating dialogs + +``s are set to `display: none;` when hidden and `display: block;` when shown, as well as being removed from / added to the {{glossary("top layer")}}. Therefore, for `` elements to be animated `display` needs to be animatable. This is now the case; [supporting browsers](/en-US/docs/Web/CSS/display#browser_compatibility) animate `display` with a variation on the [discrete animation type](/en-US/docs/Web/CSS/CSS_animated_properties#discrete). Specifically, the browser will flip between `none` and another value of `display` so that the animated content is shown for `100%` of the animation duration. So for example: + +- When animating between `display` `none` and `block`, the value will flip to `block` at `0%` of the animation duration so it is visible throughout. +- When animating between `display` `block` and `none`, the value will flip to `none` at `100%` of the animation duration so it is visible throughout. + +#### Transitioning dialog elements + +When animating ``s with [CSS transitions](/en-US/docs/Web/CSS/CSS_transitions), the following are required: + +- [`@starting-style`](/en-US/docs/Web/CSS/@starting-style) is used to provide a set of starting values for properties set on the `` that you want to transition from when it is shown. This is needed because, by default, [CSS transitions](/en-US/docs/Web/CSS/CSS_transitions) are not triggered on elements' first style updates, or when the `display` type changes from `none` to another type, to avoid unexpected behavior. +- [`display`](/en-US/docs/Web/CSS/display) needs to be added to the transitions list so that the `` will remain as `display: block` for the duration of the animation, allowing the other animations to be seen. +- [`overlay`](/en-US/docs/Web/CSS/overlay) needs to be added to the transitions list so that the removal of the `` from the top layer will be deferred until the animation is finished, again allowing the other animations to be seen. +- [`transition-behavior: allow-discrete`](/en-US/docs/Web/CSS/transition-behavior) needs to be set on the `display` and `overlay` transitions. This effectively enables discrete transitions. + +Here is a quick example to show what this might look like. + +The HTML contains a `` element, plus a button to show the dialog. Additionally, the `` element contains another button to close itself. + +```html + + Content here + + + + +``` + +The CSS is as follows: + +```css +/* Open state of the dialog */ +dialog[open] { + opacity: 1; + transform: scaleY(1); +} + +/* Closed state of the dialog */ +dialog { + transition: + opacity 0.5s ease-out, + transform 0.5s ease-out, + overlay 0.5s ease-out allow-discrete, + display 0.5s ease-out allow-discrete; + opacity: 0; + transform: scaleY(0); +} + +/* Before-open state */ +/* Needs to be after the previous dialog[open] rule to take effect, + as the specificity is the same */ +@starting-style { + dialog[open] { + opacity: 0; + transform: scaleY(0); + } +} + +/* Transition the :backdrop when the dialog modal is promoted to the top layer */ +dialog::backdrop { + background-color: rgba(0, 0, 0, 0); + transition: + display 0.7s allow-discrete, + overlay 0.7s allow-discrete, + background-color 0.7s; +} + +dialog[open]::backdrop { + background-color: rgba(0, 0, 0, 0.25); +} + +/* This starting-style rule cannot be nested inside the above selector +because the nesting selector cannot represent pseudo-elements. */ + +@starting-style { + dialog[open]::backdrop { + background-color: rgba(0, 0, 0, 0); + } +} + +body, +button { + font-family: system-ui; +} +``` + +Note the `@starting-style` block used to define the transition starting styles, and the `display` and `overlay` properties included in the transition list, each with `allow-discrete` set on them. Note that we've also included similar CSS to transition the [`::backdrop`](/en-US/docs/Web/CSS/::backdrop) that appears behind the `` when it opens, to provide a nice darkening animation. `dialog[open]::backdrop` is required to select the backdrop when the dialog is open. + +The JavaScript serves to wire up the buttons to event handlers that show and close the ``: + +```js +const dialogElem = document.getElementById("dialog"); +const showBtn = document.querySelector(".show"); +const closeBtn = document.querySelector(".close"); + +showBtn.addEventListener("click", () => { + dialogElem.showModal(); +}); + +closeBtn.addEventListener("click", () => { + dialogElem.close(); +}); +``` + +The code renders as follows: + +{{ EmbedLiveSample("Transitioning dialog elements", "100%", "200") }} + +#### dialog keyframe animations + +When animating a `` with [CSS animations](/en-US/docs/Web/CSS/CSS_animations), there are some differences to note from transitions: + +- You don't provide a starting state inside a `@starting-style` block; instead, you need to provide the starting `display` value in an explicit starting keyframe (for example using `0%` or `from`). +- You don't need to enable discrete transitions; there is no equivalent to `allow-discrete` inside keyframes. +- You can't set `overlay` inside keyframes either. The best solution we found at this time to defer removal from the top layer was to use JavaScript — animating the `` and then using {{domxref("setTimeout()")}} to defer removal of the `` until after the animation is finished. + +Let's have a look at an example so you can see what this looks like. First, the HTML contains a `` element, plus a button to show the dialog. Additionally, the `` element contains another button to close itself. The `` has a `class` of `fade-in` set on it, which will trigger the entry animation when the dialog is shown. + +```html + + Content here + + + + +``` + +Now for the CSS: + +```css +/* Animation classes */ + +.fade-in { + animation: fade-in 0.5s ease-out forwards; +} + +.fade-out { + animation: fade-out 0.5s ease-out forwards; +} + +/* Animation keyframes */ + +@keyframes fade-in { + 0% { + opacity: 0; + transform: scaleY(0); + display: none; + } + + 100% { + opacity: 1; + transform: scaleY(1); + display: block; + } +} + +@keyframes fade-out { + 0% { + opacity: 1; + transform: scaleY(1); + display: block; + } + + 100% { + opacity: 0; + transform: scaleY(0); + display: none; + } +} + +body, +button { + font-family: system-ui; +} +``` + +Note the keyframes defined to animate between the closed and open states, which are then applied to control classes. This includes animating `display` to make sure the actual visible animation effects remain visible for the whole duration. This example doesn't animate the backdrop like the transitions example above does — that wasn't possible to reproduce with a keyframe animation at the time of writing. + +Finally, the JavaScript serves to wire up the buttons to event handlers that show and close the ``, while also adding and removing the control classes as required to apply the entry and exit animations. In addition, note how {{domxref("setTimeout()")}} is used to defer closing the `` until the exit animation is finished. + +```js +const dialogElem = document.getElementById("dialog"); +const showBtn = document.querySelector(".show"); +const closeBtn = document.querySelector(".close"); + +showBtn.addEventListener("click", () => { + dialogElem.classList.remove("fade-out"); + dialogElem.classList.add("fade-in"); + dialogElem.showModal(); +}); + +closeBtn.addEventListener("click", () => { + dialogElem.classList.remove("fade-in"); + dialogElem.classList.add("fade-out"); + setTimeout(() => { + dialogElem.close(); + }, 500); +}); +``` + +The code renders as follows: + +{{ EmbedLiveSample("dialog keyframe animations", "100%", "200") }} + ## Technical summary diff --git a/files/jsondata/GroupData.json b/files/jsondata/GroupData.json index 302ccda5edecdcd..efde98ceac0bc31 100644 --- a/files/jsondata/GroupData.json +++ b/files/jsondata/GroupData.json @@ -293,6 +293,7 @@ "CSSRule", "CSSRuleList", "CSSStyleDeclaration", + "CSSStartingStyleRule", "CSSStyleRule", "CSSStyleSheet", "CSSSupportsRule",