Flexible Menü-Animation mit verankerten Container Queries
Chrome hat vor kurzem die neue Version 143 mit Support für verankerte Container-Queries in CSS veröffentlicht. Mit diesen Abfragen können wir den Inhalt eines verankerten Elements basierend auf der aktuellen Position relativ zum Anker stylen. Einfach cool! 🤩
Vor ein paar Monaten habe ich einen Artikel über die Erstellung eines barrierefreien Menüs mit der Popover API und CSS Anchor Positioning veröffentlicht. Damals habe ich gejammert, dass ich auf JavaScript zurückgreifen muss, um die Skalierungs-Animation für verschiedene Positionen des Menüfelds relativ zum Menübutton umzusetzen.
Jetzt ist das nicht mehr nötig! Ich zeige euch, wie ihr allein mit CSS die Animation des Menüfelds definieren könnt.
Foto: © Snapwire / pexels.com
Die Grundlagen verankerter Container Queries
Das Dokument CSS Anchor Positioning Module Level 2 definiert den neuen
Wert anchored für die container-type Eigenschaft. Konkret heißt es:
Establishes a query container for container queries, allowing for descendants of an anchor positioned element to be styled based on certain features of the anchoring. (Currently, limited to which of the position-try-fallbacks are applied, if any.)In unserem Fall haben wir ein benutzerdefiniertes Menüfeld, das mit dem Menü-Button als Anker verbunden ist. Wenn
wir container-type: anchored für das Menüfeld setzen, wird es zum Abfrage-Container für die Position
des verankerten Elements.
Dadurch können wir den Skalierungs-Effekt des Menüfelds an seine aktuelle Position anpassen, indem wir die neue
At-Regel @container anchored(fallback: ...) verwenden. Beispiel: Wenn das Menüfeld oben links neben
der Schaltfläche platziert ist, lassen wir es aus der unteren rechten Ecke herauswachsen.
Bevor wir uns dem HTML- und CSS-Code widmen, werft erstmal einen Blick auf die fertige Demo des benutzerdefinierten Menüs.
Demo: Barrierefreies Menü mit reiner CSS-Animation
Ich habe eine neue CodePen-Demo erstellt, die viermal das gleiche Menüelement enthält. Die Menü-Buttons sind grob bei den vier Ecken des Bildschirms platziert, damit ihr leicht testen könnt, wie die Animation des Menüfelds dessen aktuelle Position berücksichtigt:
Wenn euer Browser keine verankerten Container Queries unterstützt, wird das Menüfeld einfach nur eingeblendet. Ich zeige euch, wie ihr den Skalierungseffekt als progressive Verbesserung implementiert.
Wie ihr flexible Animationen mit verankerten Container Queries baut
Ich werde nicht alle Details meines barrierefreien Custom-Menüs wiederholen. Lest bitte zuerst meinen Artikel „Wir bauen ein barrierefreies Menü mit modernen Web-Features“, bevor es hier weitergeht.
Schritt 1: Die grundlegende HTML-Struktur
Damit die verankerte Container Query funktioniert, müssen wir die HTML-Struktur unseres Menüs leicht anpassen. Ich habe
zunächst versucht, direkt die Styles des div[role="menu"]-Elements zu ändern, aber es hat nicht
funktioniert. Warum ist das so?
Die offizielle Spezifikation sagt eindeutig, dass eine verankerte Container Query nur das Stylen der Inhalte des positionierten Elements erlaubt. Das bedeutet, dass wir einen zusätzlichen inneren Container benötigen:
<button id="menu-btn-1" class="menu-btn" aria-label="More options" popovertarget="menu-content-1" type="button">
<span>/* icon */</span>
</button>
<div id="menu-content-1" role="menu" aria-labelledby="menu-btn-1" popover>
<div class="menu-inner-box">
<button role="menuitem" type="button">...</button>
<button role="menuitem" type="button">...</button>
<button role="menuitem" type="button">...</button>
</div>
</div>
Das .menu-inner-box-Element ist ein Kind-Element des Menüfelds. Daher können wir
dessen transform-origin beliebig ändern, abhängig von der aktuellen Anker-Positionierung.
Schritt 2: Animation des Menüfelds
Das Menüfeld sollte eingeblendet und vergrößert werden, wenn es geöffnet wird. Da wir die Transition auf ein Popover anwenden,
das von display: none zu display: flex wechselt, müssen wir auch die
Eigenschaften overlay und display berücksichtigen. So sieht das Basis-Setup
für das Menüfeld aus:
div[role="menu"] {
/* General animation and end of fade-out */
opacity: 0;
transition-property: opacity, overlay, display;
transition-behavior: allow-discrete;
transition-duration: 100ms;
transition-timing-function: linear;
}
Wir definieren die Skalierungs-Animation separat für den inneren Container. Da das Menüfeld standardmäßig unterhalb und rechts
vom Menübutton positioniert wird, legen wir für den Skalierungseffekt transform-origin: top left fest:
div[role="menu"] > .menu-inner-box {
transform: scale(1);
transform-origin: top left;
transition-property: transform;
transition-duration: inherit;
transition-timing-function: inherit;
}
Die Einblende-Animation soll 120 Millisekunden dauern und eine weiche kubisch-beziersche Zeitfunktion verwenden. Am Ende des Übergangs ist das Popover vollständig sichtbar und auf 100 Prozent skaliert:
div[role="menu"]:popover-open {
/* End of fade-in and start of fade-out */
transition-duration: 120ms;
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
opacity: 1;
}
div[role="menu"]:popover-open > .menu-inner-box {
transform: scale(1);
}
Da das Menüfeld von einem verborgenen in einen sichtbaren Zustand wechselt, gibt es keine berechneten Werte, die der Übergang
als Ausgangspunkt verwenden kann. Daher nutzen wir @starting-style, um die initiale Deckkraft auf 0
und die anfängliche Skalierung auf 80 Prozent zu setzen:
/* Start of fade-in */
@starting-style {
div[role="menu"]:popover-open {
opacity: 0;
}
div[role="menu"]:popover-open > .menu-inner-box {
transform: scale(0.8);
}
}
Damit funktioniert die Skalierungs-Animation schon mal für die Standardplatzierung des Menüfelds. Als Nächstes verwenden wir verankerte Container Queries, um die Animation an verschiedene Positionen anzupassen.
Schritt 3: Positions-Fallbacks
Unser Menü-Button ist bereits implizit als Ankerelement des Menüfelds definiert. Daher reicht es, wenn wir eine Default-Position sowie mehrere Fallbacks für das Menüfeld definieren:
div[role="menu"] {
position: fixed;
position-area: end span-end;
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
}
Wenn das verankerte Element (das Menüfeld) seinen Containing Block überläuft, probiert der Browser automatisch die Liste
der alternativen Positionen aus, die mithilfe der Eigenschaft position-try-fallbacks definiert wurden.
Er verwendet die erste Alternative, die nicht über den Containing Block bzw. den Viewport hinausragt.
Wie können wir erkennen, welche Fallback-Position vom Browser ausgewählt wurde? Zunächst machen wir das Menüfeld zum Abfrage-Container für seine Ankerposition:
div[role="menu"] {
container-type: anchored;
}
Jetzt können wir den aktiven Fallback des verankerten Elements abfragen und die Animation entsprechend anpassen. Hier ist ein Beispiel:
@container anchored(fallback: flip-block) {
div[role="menu"] > .menu-inner-box {
transform-origin: bottom left;
}
}
Diese At-Regel wird wirksam, wenn die Position des Menüfelds nur in der Blockachse umgedreht wird. Das Menüfeld befindet sich nun oberhalb des Menü-Buttons und wirkt beim Einblenden so, als würde es von unten links herauswachsen. Genial! 😍
Die restlichen Container Queries, die wir für alle Positionen benötigen, findet ihr im CSS-Code meiner CodePen-Demo.
Schritt 4: Progressive Verbesserung
Zu guter Letzt möchten wir sicherstellen, dass die Animation des Menüfelds auch funktioniert, wenn ein Browser noch keine
verankerten Container Queries unterstützt. Mit der @supports At-Regel definieren wir den
Skalierungseffekt sowie die Container Queries nur dann, wenn der Browser diese unterstützt:
@supports (container-type: anchored) {
div[role="menu"] {
container-type: anchored;
}
/* Styles related to the scaling effect animation */
}
Progressive Verbesserung war noch nie so einfach!
Nützliche Links
- Using CSS anchor positioning (MDN)
- position-try-fallbacks (MDN)
- Detect fallback positions with anchored container queries from Chrome 143
Erstellt am