In der Vergangenheit konnten CSS-Übergänge Elemente nicht animieren, wenn sie zum ersten Mal dem DOM hinzugefügt wurden oder wenn sich ihre display-Eigenschaft von none geändert hat. Die At-Regel @starting-style und transition-behavior: allow-discrete bieten eine deklarative Möglichkeit, flüssige Ein- und Ausblendanimationen zu erstellen.
Implementierung
1. display: none Umschaltflächen animieren
So animieren Sie ein Element, wenn Sie seine Sichtbarkeit über ein Attribut umschalten (z.B. hidden mit display: none):
- Sichtbaren Zustand definieren: Legen Sie die endgültigen Attributwerte (z.B.
opacity: 1) für die Basisklasse fest. - Anfangsstatus für den Einstieg definieren: Verwenden Sie
@starting-style, um die Werte anzugeben, von denen der Übergang erfolgen soll, wenn das Element sichtbar wird. - Diskrete Übergänge aktivieren: Fügen Sie
displayin die Propertytransitionein und verwenden Sietransition-behavior: allow-discrete. - Endzustand definieren: Legen Sie die Zielwerte im Attribut
hiddenfest.
.card {
display: block;
opacity: 1;
translate: 0;
/* MANDATORY: Use transition-behavior: allow-discrete for display transition */
transition:
display 0.4s,
opacity 0.4s ease-out,
translate 0.4s ease-out;
transition-behavior: allow-discrete;
}
/* Entry animation: transition FROM these values when first rendered */
@starting-style {
.card {
opacity: 0;
translate: 0 -20px;
}
}
/* Exit animation: transition TO these values when hidden */
.card[hidden] {
display: none;
opacity: 0;
translate: 0 -20px;
}
/* Respect user preference for reduced motion */
@media (prefers-reduced-motion: reduce) {
.card {
/* Disable movement and shorten duration for a simple fade */
translate: none;
transition-duration: 0.1s;
}
@starting-style {
.card {
translate: none;
}
}
.card[hidden] {
translate: none;
}
}
2. Animation beim Einfügen und Entfernen von DOM-Elementen
Für Elemente, die über appendChild() hinzugefügt oder über remove() entfernt wurden:
- Eintrag: Verwenden Sie
@starting-stylewie oben gezeigt. Der Browser erkennt die Stiländerung von „nichts“ zu den ursprünglichen Stilen des Elements automatisch und löst den Übergang von den@starting-style-Werten aus. - Entfernen: Da
element.remove()sofort erfolgt und von sich aus keinen CSS-Übergang auslöst, müssen Sie zuerst den Exit-Übergang auslösen und warten, bis er abgeschlossen ist, bevor Sie den Knoten aus dem DOM entfernen.
// 1. Trigger exit transition
element.hidden = true;
// 2. Wait for all active transitions/animations to finish,
// with a failsafe timeout in case an animation never ends (e.g. for looping animations)
const animations = element.getAnimations();
if (animations.length > 0) {
await Promise.race([
// Promise.allSettled ensures we wait even if some animations fail
Promise.allSettled(animations.map(a => a.finished)),
new Promise(r => setTimeout(r, 2000))
]);
}
// 3. Finally remove the node from the DOM
element.remove();
Einschränkungen und Barrierefreiheit
- OBLIGATORISCH: Verwende
transition-behavior: allow-discrete, wenn dudisplayumstellst. Ohne diese Eigenschaft verschwindet das Element beim Beenden sofort. - Verwenden Sie NICHT
allow-discretein der Kurzschreibweisetransition, da ältere Browser sonst die gesamtetransition-Deklaration ignorieren. Verwenden Sie eine separatetransition-behavior: allow-discrete-Deklaration, außer in Anwendungsfällen, in denen dies erwünscht ist. - VERPFLICHTEND: Verwenden Sie
@starting-stylefür Einblendungsanimationen. Browser überspringen Übergänge bei der ersten Stilaktualisierung eines Elements (erstes Rendering oder Änderung vondisplay: none), sofern dies nicht angegeben ist. - EMPFOHLEN: Nehmen Sie
overlayin die Listetransitionauf, wenn Sie Elemente der obersten Ebene wie<dialog>oderpopoveranimieren, damit sie während der Ausgangsanimation in der obersten Ebene bleiben. - EMPFOHLEN: Berücksichtigen Sie die Nutzereinstellungen für reduzierte Bewegung mit der Medienabfrage
prefers-reduced-motion. - NICHT:
@starting-stylefür Ausblendanimationen verwenden. Damit wird nur der Startpunkt für einen Einblendübergang definiert. Ausblendungsanimationen werden durch den Übergang zum ausgeblendeten Zustand definiert.
Fallback-Strategien
Fallbacks und Browserunterstützung für „transition-behavior“
Firefox 129+ parst transition-behavior: allow-discrete (CSS.supports('transition-behavior', 'allow-discrete') gibt true zurück), ohne die display-Property tatsächlich zu übergeben (Firefox-Fehler 1882408). Dadurch verschwinden Elemente beim Beenden sofort.
Wenn Sie die Unterstützung für diskrete display-Übergänge zuverlässig erkennen möchten, prüfen Sie, ob der berechnete display-Wert eines temporären Elements sichtbar bleibt, wenn es zu none übergeht, anstatt sich nur auf CSS.supports('transition-behavior', 'allow-discrete') zu verlassen:
let supportsDisplayTransition;
function canTransitionDisplay() {
if (supportsDisplayTransition !== undefined) return supportsDisplayTransition;
if (!window.CSS?.supports?.('transition-behavior', 'allow-discrete') || !document.body) {
return false;
}
const probe = document.createElement('div');
// The shorthand is intentional here: browsers that don't parse allow-discrete
// drop the whole declaration, so display: none applies instantly and the probe
// correctly returns false. !important guards against global reduced-motion
// resets like `* { transition: none !important }`.
probe.style.cssText = 'transition: display 1s allow-discrete !important; display: block;';
document.body.appendChild(probe);
getComputedStyle(probe).display;
probe.style.display = 'none';
supportsDisplayTransition = getComputedStyle(probe).display === 'block';
probe.remove();
return supportsDisplayTransition;
}
Fallback beenden, wenn diskrete display-Übergänge nicht unterstützt werden
Eingangs-Animationen mit @starting-style funktionieren in allen modernen Browsern ohne JavaScript. Wenn diskrete display-Übergänge nicht unterstützt werden (!canTransitionDisplay()), wird durch das Festlegen von hidden sofort display: none angewendet und die Animation zum Ausblenden übersprungen (sowohl beim Ausblenden als auch vor element.remove()). Trennen Sie den visuellen Zustand beim Ausblenden ([data-closing]) von display: none ([hidden]), damit opacity und translate die Animation beenden, bevor das Element ausgeblendet oder entfernt wird. Ersetzen Sie die .card[hidden]-Regeln aus Schritt 1 (einschließlich der prefers-reduced-motion-Überschreibung) durch Folgendes:
.card:where([hidden], [data-closing]) {
opacity: 0;
translate: 0 -20px;
}
.card[hidden] {
display: none;
}
@media (prefers-reduced-motion: reduce) {
.card:where([hidden], [data-closing]) {
translate: none;
}
}
async function hideElement(el) {
if (canTransitionDisplay()) {
el.hidden = true;
} else {
el.setAttribute('data-closing', '');
}
const animations = el.getAnimations();
if (animations.length > 0) {
await Promise.race([
Promise.allSettled(animations.map((a) => a.finished)),
new Promise((r) => setTimeout(r, 2000)),
]);
}
// Skipped if showElement() cancelled the close mid-animation.
if (el.hasAttribute('data-closing')) {
el.removeAttribute('data-closing');
el.hidden = true;
}
}
function showElement(el) {
el.removeAttribute('data-closing');
el.hidden = false;
}
async function removeElement(el) {
await hideElement(el);
el.remove();
}