<h1 id="title">In&Out element transitions with @starting-style</h1>
<button id="toggleTitle">Toggle [hidden] on title</button>
<p>Add an item below, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@starting-style"><code>@starting-style</code></a> describes the element before it's inserted into the page.</p>
<section id="grid">
<div></div>
</section>
<div>
<button id="add">Add</button>
<button id="remove">Remove</button>
<button id="toggleContent">Toggle [hidden] on items</button>
</div>
<p>These buttons toggle the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/hidden">[hidden]</a> attribute on various elements, which sets display to none and opacity to 0. Thanks to <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transition-behavior">transition-behavior</a>, none is delayed.</p>
<p>It also works on dialog and popover elements</p>
<div>
<button id="toggleDialog">Show a dialog</button>
<button id="togglePopover">Show a popover</button>
</div>
<dialog id="dialogExample">
<div>
<h2>Are you sure?</h2>
<button id="closeDialog">Close</button>
</div>
</dialog>
<pop-up popover id="popup">
<h4>Pop!</h4>
</pop-up>
@import "https://unpkg.com/open-props/easings.min.css";
/* use an anonymous layer to demote the effect */
@layer {
* {
@media (prefers-reduced-motion: no-preference) {
transition:
opacity .5s ease-in,
scale .5s ease-in,
display .5s ease-in;
/* key to transitioning out */
transition-behavior: allow-discrete;
}
/* stage enter */
/* key to transitioning in */
@starting-style {
opacity: 0;
scale: 1.1;
}
/* stage exit */
/* use your own technique here */
&[hidden],
dialog:not(:modal),
&[popover]:not(:popover-open) {
opacity: 0;
scale: .9;
/* hidden sets display: none, but loses easily */
display: none !important;
/* faster leaving the stage then entering */
transition-duration: .4s;
transition-timing-function: var(--ease-out-5);
}
}
}
@layer support {
* {
box-sizing: border-box;
margin: 0;
}
html {
block-size: 100%;
color-scheme: dark light;
}
body {
min-block-size: 100%;
font-family: system-ui, sans-serif;
display: grid;
place-content: center;
place-items: start;
gap: 1rlh;
}
p, section, ul {
max-inline-size: 40ch;
}
h1 {
max-inline-size: 15ch;
}
ul, h1 {
text-wrap: balance;
}
section {
display: flex;
flex-wrap: wrap;
gap: 10px;
> div {
background: CanvasText;
inline-size: 10ch;
aspect-ratio: 1;
}
}
dialog, [popover] {
margin: auto;
}
dialog > div {
display: grid;
place-items: end;
gap: 1rlh;
}
}
// these functions demonstrate mutations
// so we can observe how the CSS helps transition
add.onclick = () =>
grid.appendChild(document.createElement('div'))
remove.onclick = async () => {
let last = grid?.lastElementChild
last.hidden = true
await onTransitionsEnded(last)
last.remove()
}
toggleTitle.onclick = () =>
title.hidden = !title.hidden
toggleContent.onclick = () =>
grid.hidden = !grid.hidden
toggleDialog.onclick = () =>
dialogExample.showModal()
closeDialog.onclick = async () =>
dialogExample.close()
togglePopover.onclick = () =>
popup.showPopover()
function onTransitionsEnded(node) {
return Promise.allSettled(
node.getAnimations().map(animation =>
animation.finished))
}
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.