Mobile Navigation in Hyvä als Alpine-Komponente
AI generated
Hyvä
phtml
Hyvä Themes · Alpine.js · Magento 2 · Barrierefreiheit
Mobile Navigation in Hyvä als Alpine-Komponente
Zustände, Fokus und Animation sauber verdrahtet

Eine mobile Navigation, die nur ein- und ausblendet, reicht nicht: Ohne Zustandsmodell für verschachtelte Kategorien, ohne Fokus-Trap und ohne Scroll-Lock wird das Off-Canvas-Menü für Tastatur- und Screenreader-Nutzer schnell zur Sackgasse. Dieser Beitrag zeigt, wie die mobile Navigation in einem Hyvä-Theme als eigenständige Alpine-Komponente entsteht - mit Zustands-Stack für Kategorieebenen, x-transition für das Slide-in-Panel, Fokus-Trap, ARIA-Attributen und performanter CSS-Transform-Animation statt Layout-Thrashing.

14 Min. Lesezeit Alpine.js · x-transition · Fokus-Trap · ARIA Magento 2.4.8-p4 · Hyvä Themes · Tailwind CSS v4

1. Warum die mobile Navigation eine eigene Alpine-Komponente braucht

Die mobile Navigation ist in den meisten Magento-Shops die am häufigsten genutzte Interaktion auf Smartphones - noch vor der Produktsuche. Trotzdem wird sie in vielen Umsetzungen als Nebenprodukt des Desktop-Headers behandelt: ein Hamburger-Icon, ein x-show, fertig. Das reicht für die reine Sichtbarkeit, ignoriert aber die eigentliche Komplexität - verschachtelte Kategoriebäume, Tastaturbedienung, Screenreader-Kompatibilität und die Frage, was beim Zurück-Navigieren durch mehrere Ebenen mit dem Zustand passiert. Eine mobile Navigation als Alpine-Komponente kapselt genau diese Logik an einer Stelle, statt sie über mehrere Templates und Ad-hoc-Klassen zu verteilen.

In Hyvä ersetzt Alpine.js das Knockout.js-Widget-System von Luma vollständig. Für die mobile Navigation bedeutet das: kein UI-Component-Overhead, keine Observable-Ketten, sondern ein einziges Alpine.data-Objekt, das Zustand, Übergänge und Verhalten der Off-Canvas-Navigation bündelt. Diese Komponente wird typischerweise in der header.phtml oder einem eigenen navigation.phtml initialisiert und über x-data an das Wurzelelement des Menüs gebunden.

Wichtig ist die Abgrenzung: Es geht hier nicht um generische Alpine-Komponenten-Architektur, nicht um Modal-Dialoge und nicht um den Store-Switcher, sondern gezielt um das Slide-in-Menü mit Kategoriebaum, wie es in nahezu jedem Hyvä-Theme als Ersatz für das Luma-Hamburger-Menü gebraucht wird. Die folgenden Abschnitte bauen die Komponente Stück für Stück auf - vom Zustandsmodell bis zur performanten Animation.

2. Das Zustandsmodell: offen, aktive Ebene und Kategorie-Stack

Der Kern jeder mobilen Navigation als Alpine-Komponente ist ein sauberes Zustandsmodell. Neben dem simplen open-Boolean für sichtbar/unsichtbar braucht die Komponente eine Struktur, die abbildet, auf welcher Kategorieebene sich der Nutzer gerade befindet und wie er per Zurück-Button wieder eine Ebene höher kommt. Die bewährte Lösung ist ein Stack, also ein Array von Kategorie-IDs, das bei jedem Tiefer-Navigieren eine ID anhängt und beim Zurück-Navigieren die letzte ID wieder entfernt (pop). Die zuletzt im Stack stehende ID bestimmt, welche Unterkategorie-Liste aktuell angezeigt wird.

Dieses Muster hat gegenüber verschachtelten Booleans einen entscheidenden Vorteil: Die Tiefe des Kategoriebaums ist beliebig, ohne dass für jede Ebene ein eigenes Flag existieren muss. Ein x-for über den Stack liefert zusätzlich die Breadcrumb-Anzeige quasi kostenlos mit. Der Zustand wird zentral in einem Alpine.data-Objekt gehalten, das per Alpine.data('mobileNav', () => ({...})) registriert und dann im Markup mit x-data="mobileNav" eingebunden wird - so bleibt die mobile Navigation testbar und von der Darstellung entkoppelt.

Neben open und dem Kategorie-Stack gehört auch ein Verweis auf das aktuell fokussierte Element und ein Flag für den initialen Fokus in den Zustand. Diese Werte werden im nächsten Abschnitt für Fokus-Trap und Tastaturnavigation gebraucht. Wichtig: Der Zustand darf nicht mit DOM-Abfragen vermischt werden - alles, was die Sichtbarkeit oder aktive Ebene betrifft, gehört in reaktive Alpine-Properties, nicht in manuelle Klassenmanipulation.


document.addEventListener('alpine:init', () => {
  Alpine.data('mobileNav', () => ({
    // Whether the off-canvas panel is visible
    open: false,

    // Stack of category IDs representing the navigation depth,
    // e.g. [12, 45] means: root -> category 12 -> category 45
    categoryStack: [],

    // Element that had focus before the menu was opened,
    // restored on close for a predictable focus flow
    triggerElement: null,

    /**
     * Opens the mobile navigation panel and remembers the
     * element that triggered it so focus can return later.
     */
    openMenu() {
      this.triggerElement = document.activeElement
      this.open = true
    },

    /**
     * Closes the panel, resets the category stack and
     * restores focus to the trigger element (the hamburger button).
     */
    closeMenu() {
      this.open = false
      this.categoryStack = []
      this.triggerElement?.focus()
    },

    /**
     * Navigates one level deeper into the category tree.
     * @param {number} categoryId - id of the category to descend into
     */
    goDeeper(categoryId) {
      this.categoryStack.push(categoryId)
    },

    /**
     * Navigates one level back up the category tree.
     */
    goBack() {
      this.categoryStack.pop()
    },

    // Currently active category id, or null at the root level
    get activeCategoryId() {
      return this.categoryStack.length
        ? this.categoryStack[this.categoryStack.length - 1]
        : null
    }
  }))
})

3. x-show und x-transition für Panel und Backdrop

Für die eigentliche Sichtbarkeit der mobilen Navigation kombiniert man x-show mit x-transition, statt das Element per x-if aus dem DOM zu entfernen. Der Grund: Fokus-Management und Scroll-Lock funktionieren zuverlässiger, wenn das Panel im DOM bleibt und nur über display: none ausgeblendet wird. Zwei Elemente brauchen eigene Übergänge: der halbtransparente Backdrop, der mit einem einfachen Fade ein- und ausblendet, und das Slide-in-Panel selbst, das von der Seite hereinfährt.

Bei der Konfiguration von x-transition:enter und x-transition:leave lohnt sich eine klare Trennung der Timing-Werte: Das Öffnen darf minimal langsamer sein als das Schließen, damit die mobile Navigation beim Öffnen ruhig wirkt, beim Schließen aber zügig aus dem Weg geht. Die Modifier .duration steuern das direkt im Markup, ohne zusätzliches CSS. Wichtig ist außerdem, dass Backdrop und Panel unabhängig, aber synchron auf denselben open-Zustand reagieren - zwei getrennte x-show-Direktiven auf demselben Boolean, keine doppelte State-Verwaltung.


<!-- app/design/frontend/Mironsoft/default/Magento_Theme/templates/navigation/mobile-nav.phtml -->
<div x-data="mobileNav" x-cloak>

  <button
    type="button"
    @click="openMenu()"
    :aria-expanded="open.toString()"
    aria-controls="mobile-nav-panel"
    class="lg:hidden p-2"
  >
    <span class="sr-only">Menu öffnen</span>
    <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
    </svg>
  </button>

  <!-- Backdrop: simple fade -->
  <div
    x-show="open"
    x-transition:enter="transition-opacity ease-out duration-200"
    x-transition:enter-start="opacity-0"
    x-transition:enter-end="opacity-100"
    x-transition:leave="transition-opacity ease-in duration-150"
    x-transition:leave-start="opacity-100"
    x-transition:leave-end="opacity-0"
    @click="closeMenu()"
    class="fixed inset-0 bg-black/50 z-40"
  ></div>

  <!-- Slide-in panel: transform-based animation -->
  <div
    id="mobile-nav-panel"
    x-show="open"
    x-transition:enter="transition-transform ease-out duration-250"
    x-transition:enter-start="-translate-x-full"
    x-transition:enter-end="translate-x-0"
    x-transition:leave="transition-transform ease-in duration-200"
    x-transition:leave-start="translate-x-0"
    x-transition:leave-end="-translate-x-full"
    class="fixed inset-y-0 left-0 w-80 max-w-[85vw] bg-white z-50 overflow-y-auto"
    style="will-change: transform;"
  >
    <!-- category tree renders here, see section 7 -->
  </div>
</div>

4. Fokus-Trap und Escape-Handling für Tastaturnutzer

Ohne Fokus-Trap kann ein Tastaturnutzer mit Tab aus einer geöffneten mobilen Navigation heraus in den verdeckten Seiteninhalt springen - ein klassischer Barrierefreiheitsfehler, der bei einem simplen x-show-Ansatz fast immer übersehen wird. Der Fokus-Trap sorgt dafür, dass Tab und Shift+Tab innerhalb des Panels zirkulieren, solange die mobile Navigation geöffnet ist, und dass beim Öffnen automatisch das erste fokussierbare Element (meist der Schließen-Button) den Fokus erhält.

Praktisch lässt sich das mit einem kleinen Alpine-Helper umsetzen, der beim keydown.tab.window prüft, ob der aktuelle Fokus innerhalb des Panels liegt, und bei Bedarf zum ersten oder letzten fokussierbaren Element springt. Ergänzend schließt @keydown.escape.window="closeMenu()" die mobile Navigation unabhängig davon, welches Element gerade fokussiert ist - ein Standardverhalten, das Nutzer aus jedem anderen Overlay im Web kennen und erwarten.

Die Liste der fokussierbaren Elemente muss bei jedem Wechsel der Kategorieebene neu ermittelt werden, da sich der sichtbare Inhalt des Panels ändert. Ein einfacher Selektor wie a[href], button:not([disabled]) auf das Panel-Element genügt in den meisten Fällen; komplexere Widgets im Menü (z. B. ein Suchfeld) sollten explizit mit einbezogen werden.


document.addEventListener('alpine:init', () => {
  Alpine.data('mobileNav', () => ({
    open: false,
    categoryStack: [],
    triggerElement: null,

    /**
     * Returns all focusable elements currently visible inside the panel.
     * Recomputed on every render because the visible level changes.
     * @returns {HTMLElement[]}
     */
    getFocusableElements() {
      const panel = this.$refs.panel
      return Array.from(
        panel.querySelectorAll('a[href], button:not([disabled]), input:not([disabled])')
      ).filter((el) => el.offsetParent !== null)
    },

    /**
     * Focus trap: keeps Tab / Shift+Tab cycling inside the panel
     * while the mobile navigation is open.
     * @param {KeyboardEvent} event
     */
    trapFocus(event) {
      if (!this.open) return
      const focusable = this.getFocusableElements()
      if (focusable.length === 0) return

      const first = focusable[0]
      const last = focusable[focusable.length - 1]

      if (event.shiftKey && document.activeElement === first) {
        event.preventDefault()
        last.focus()
      } else if (!event.shiftKey && document.activeElement === last) {
        event.preventDefault()
        first.focus()
      }
    },

    openMenu() {
      this.triggerElement = document.activeElement
      this.open = true
      this.$nextTick(() => this.getFocusableElements()[0]?.focus())
    },

    closeMenu() {
      this.open = false
      this.categoryStack = []
      this.triggerElement?.focus()
    }
  }))
})

5. Body-Scroll-Lock, solange die mobile Navigation offen ist

Wenn die mobile Navigation geöffnet ist, darf der Hintergrund nicht mitscrollen - sonst verliert der Nutzer die Orientierung, sobald das Menü wieder schließt, und auf iOS kommt es ohne Lock häufig zu einem sichtbaren "Rubber-Banding" des Hintergrunds. Die sauberste Lösung in Alpine ist ein x-effect, das reaktiv auf Änderungen von open reagiert und die Klasse overflow-hidden auf dem body-Element toggelt, statt das in jeder openMenu()/closeMenu()-Methode einzeln zu wiederholen.

Ein häufiger Fehler beim Scroll-Lock: Nur overflow: hidden auf dem body zu setzen, reicht auf iOS Safari nicht zuverlässig aus. Zusätzlich muss die aktuelle Scroll-Position gespeichert, der body auf position: fixed gesetzt und beim Schließen die Scroll-Position wiederhergestellt werden. Ohne diesen zusätzlichen Schritt springt die Seite beim Schließen der mobilen Navigation sichtbar an eine andere Stelle.


document.addEventListener('alpine:init', () => {
  Alpine.data('mobileNav', () => ({
    open: false,
    categoryStack: [],
    scrollY: 0,

    /**
     * Alpine lifecycle hook: sets up a reactive effect that locks
     * or unlocks page scrolling whenever `open` changes.
     */
    init() {
      this.$watch('open', (isOpen) => {
        if (isOpen) {
          this.scrollY = window.scrollY
          document.body.style.position = 'fixed'
          document.body.style.top = `-${this.scrollY}px`
          document.body.classList.add('overflow-hidden', 'w-full')
        } else {
          document.body.style.position = ''
          document.body.style.top = ''
          document.body.classList.remove('overflow-hidden', 'w-full')
          window.scrollTo(0, this.scrollY)
        }
      })
    },

    openMenu() {
      this.open = true
    },

    closeMenu() {
      this.open = false
      this.categoryStack = []
    }
  }))
})

6. ARIA-Attribute: aria-expanded, aria-hidden und role=dialog

Ohne korrekte ARIA-Attribute bleibt die mobile Navigation für Screenreader-Nutzer schwer bedienbar, selbst wenn Fokus-Trap und Scroll-Lock einwandfrei funktionieren. Der Hamburger-Button braucht aria-expanded, das dynamisch an open gebunden wird, sowie aria-controls mit der ID des Panels. Das Panel selbst bekommt role="dialog" und aria-modal="true", damit Screenreader es als eigenständigen, modalen Kontext erkennen und nicht versuchen, gleichzeitig den Hintergrundinhalt vorzulesen.

Für den Rest der Seite, während die mobile Navigation geöffnet ist, empfiehlt sich zusätzlich aria-hidden="true" auf dem Hauptinhalt-Container (nicht auf dem Menü selbst). Das verhindert, dass ein Screenreader-Nutzer per virtuellem Cursor aus dem geöffneten Menü heraus in den verdeckten Hintergrund navigiert - ein Pendant zum visuellen Fokus-Trap, nur für assistive Technologien statt für Tastaturfokus. In Alpine lässt sich das elegant mit :aria-hidden="open.toString()" auf dem <main>-Element umsetzen, reaktiv an denselben Zustand gebunden wie das Panel.

Für die verschachtelten Kategorielisten selbst lohnt sich aria-current="page" auf dem aktiven Pfad und ein beschreibendes aria-label auf dem Zurück-Button, etwa "Zurück zu [übergeordnete Kategorie]" statt eines generischen "Zurück". Diese Details entscheiden in der Praxis darüber, ob die mobile Navigation tatsächlich barrierefrei bedienbar ist oder nur formal ARIA-Attribute trägt, ohne dass sie inhaltlich stimmig sind.

7. Verschachtelte Kategoriebäume rekursiv im phtml rendern

Magento-Kategoriebäume können beliebig tief verschachtelt sein, und die mobile Navigation muss diese Tiefe abbilden, ohne für jede Ebene ein separates Template zu pflegen. Die Lösung ist ein rekursives template-Fragment: Ein <template x-for>-Block iteriert über die Kategorien einer Ebene, und für Kategorien mit Kindern wird derselbe Block über ein x-if/verschachteltes template erneut aufgerufen. Alpine unterstützt diese Rekursion nativ, solange das Template als eigenständiges <template>-Element mit eigenem x-data-Scope für die jeweilige Kategorie-ID definiert ist.

Auf phtml-Seite wird der komplette Kategoriebaum idealerweise einmalig als JSON in ein x-data-Attribut oder eine <script type="application/json">-Ausgabe serialisiert, damit Alpine den Baum clientseitig ohne weitere Server-Requests durchlaufen kann. Das vermeidet Lazy-Loading-Verzögerungen beim Tiefer-Navigieren in der mobilen Navigation und hält die Interaktion sofort responsiv, unabhängig von der Baumtiefe.


<?php
/** @var \Magento\Framework\Escaper $escaper */
/** @var \Magento\Framework\View\Element\Template $block */
$categoryTreeJson = $block->getCategoryTreeJson(); // serialized via ViewModel
?>
<div
  x-data="{
    tree: <?= /* @noEscape */ $categoryTreeJson ?>,
    stack: [],
    get currentLevel() {
      let nodes = this.tree
      for (const id of this.stack) {
        const match = nodes.find(n => n.id === id)
        nodes = match ? match.children : []
      }
      return nodes
    }
  }"
  x-ref="panel"
  role="dialog"
  aria-modal="true"
  aria-label="Mobile Navigation"
>
  <button x-show="stack.length > 0" @click="stack.pop()" type="button" class="flex items-center gap-2 p-4 font-semibold">
    <span aria-hidden="true">←</span>
    <span x-text="'Zurueck'"></span>
  </button>

  <ul class="divide-y divide-slate-100">
    <template x-for="category in currentLevel" :key="category.id">
      <li>
        <div class="flex items-center justify-between p-4">
          <a :href="category.url" class="flex-1" x-text="category.name"></a>
          <button
            x-show="category.children && category.children.length"
            @click="stack.push(category.id)"
            type="button"
            :aria-label="'Untermenue ' + category.name + ' oeffnen'"
          >
            <span aria-hidden="true">→</span>
          </button>
        </div>
      </li>
    </template>
  </ul>
</div>

8. Performance: Transform statt top/left, will-change bewusst einsetzen

Die Slide-Animation der mobilen Navigation entscheidet über den gefühlten Qualitätseindruck des gesamten Themes - ruckelt das Panel beim Öffnen, wirkt der Shop sofort langsamer, als er tatsächlich ist. Der entscheidende Performance-Hebel ist, ausschließlich transform: translateX() statt left oder margin-left für die Positionsänderung zu verwenden. transform und opacity sind die einzigen CSS-Eigenschaften, die der Browser auf dem Compositor-Thread animieren kann, ohne Layout und Paint der gesamten Seite neu zu berechnen. Ein Wechsel von left löst dagegen bei jedem Frame ein vollständiges Reflow aus, was auf älteren Mobilgeräten spürbar ruckelt.

will-change: transform signalisiert dem Browser vorab, dass sich diese Eigenschaft ändern wird, und erlaubt ihm, das Element frühzeitig auf eine eigene Compositor-Ebene zu heben. Wichtig ist, will-change nicht dauerhaft auf dem Panel zu belassen, sondern es nur während der aktiven Transition zu setzen und danach wieder zu entfernen - dauerhaftes will-change auf vielen Elementen kostet Grafikspeicher und kann auf schwächeren Geräten sogar kontraproduktiv wirken. In Alpine lässt sich das über die x-transition-Hooks (@transitionstart/@transitionend) steuern.

Ein weiterer, oft übersehener Performance-Punkt: Das Umschalten von display: none auf sichtbar (was x-show intern macht) löst selbst ein Reflow aus. Deshalb sollte die Klasse für die Ausgangsposition (-translate-x-full) bereits vor dem sichtbaren Zustand gesetzt sein, damit der Browser nicht zwischen zwei Layoutberechnungen hin- und herspringt. Die in Abschnitt 3 gezeigten x-transition:enter-start/enter-end-Klassen übernehmen das automatisch, solange man sie nicht durch zusätzliche manuelle Style-Manipulation überschreibt.

9. Luma-jQuery-Menü vs. mobile Navigation als Alpine-Komponente

Der direkte Vergleich zwischen dem klassischen Luma-jQuery-Mobilmenü und einer mobilen Navigation als Alpine-Komponente macht die Unterschiede in Bundle-Größe, Barrierefreiheit und Animationsqualität greifbar. Die folgende Tabelle fasst die zentralen Kriterien zusammen, die bei der Migration von Luma auf Hyvä typischerweise geprüft werden.

Kriterium Luma-jQuery-Mobilmenü Mobile Navigation als Alpine-Komponente Auswirkung
Bundle-Größe jQuery + Menu-Widget, oft >90 KB Alpine.js Core, ~15 KB gzip Schnellerer First Load auf Mobilgeräten
Fokus-Handling Kein Fokus-Trap, Tab entkommt ins Backdrop Expliziter Fokus-Trap mit Tab-Zyklus Barrierefrei für Tastaturnutzer
Animationsbasis jQuery .animate() auf left transform via x-transition Ruckelfrei, Compositor-Thread statt Layout
Scroll-Verhalten Kein Lock, Hintergrund scrollt mit Body-Scroll-Lock via $watch Klare Orientierung nach dem Schließen
ARIA-Unterstützung Meist fehlend oder unvollständig role="dialog", aria-expanded, aria-hidden Screenreader-kompatibel

In der direkten Gegenüberstellung wird deutlich, dass es nicht nur um die Ablösung eines veralteten Frontend-Frameworks geht, sondern um eine grundsätzlich andere Herangehensweise an Zustand, Fokus und Animation. Die mobile Navigation als Alpine-Komponente ersetzt implizites jQuery-Verhalten durch explizit deklarierten, reaktiven Zustand direkt im Markup.

Mironsoft

Hyvä-Themes, Alpine-Komponenten und barrierefreie Frontends

Mobile Navigation, die wirklich barrierefrei funktioniert?

Wir bauen und optimieren die mobile Navigation in eurem Hyvä-Theme als saubere Alpine-Komponente - mit Fokus-Trap, Scroll-Lock, ARIA-Attributen und performanter Transform-Animation für jede Kategorietiefe.

Barrierefreiheits-Audit

Fokus-Trap, ARIA-Attribute und Tastaturbedienbarkeit der mobilen Navigation prüfen

Alpine-Refactoring

Bestehende jQuery-Menüs durch saubere Alpine-Komponenten mit Zustands-Stack ersetzen

Performance-Tuning

Transform-basierte Animationen und Scroll-Lock ohne Layout-Thrashing implementieren

10. Zusammenfassung

Eine belastbare mobile Navigation als Alpine-Komponente besteht aus mehr als einem Ein-/Ausblenden des Off-Canvas-Panels. Das Zustandsmodell mit einem Kategorie-Stack bildet beliebig tiefe Kategoriebäume ab, ohne dass für jede Ebene eigener Code nötig ist. x-show in Kombination mit x-transition hält Panel und Backdrop im DOM, was Fokus-Management und Scroll-Lock erst zuverlässig macht. Fokus-Trap und Escape-Handling verhindern, dass Tastaturnutzer in einen verdeckten Hintergrund geraten, während ARIA-Attribute wie role="dialog", aria-expanded und aria-hidden dieselbe Klarheit für Screenreader-Nutzer schaffen.

Auf der technischen Seite entscheidet die Wahl der Animationseigenschaft über die gefühlte Qualität: transform statt left, gezielt eingesetztes will-change und ein rekursives Template-Muster für den Kategoriebaum halten die mobile Navigation auch bei tiefen Kategoriestrukturen performant. Wer diese Bausteine konsequent kombiniert, ersetzt das fragile Luma-jQuery-Menü durch eine Komponente, die sowohl technisch robust als auch barrierefrei nutzbar ist.

Mobile Navigation in Hyvä als Alpine-Komponente - Das Wichtigste auf einen Blick

Zustandsmodell

Kategorie-Stack als Array von IDs bildet beliebige Baumtiefe ab, statt separater Booleans pro Ebene.

Fokus & Tastatur

Fokus-Trap hält Tab-Zyklus im Panel, @keydown.escape.window schließt zuverlässig von überall.

Scroll-Lock & ARIA

$watch auf open sperrt Body-Scroll; role="dialog" und aria-hidden sichern Screenreader-Klarheit.

Performance

transform: translateX() statt left, gezieltes will-change, rekursive Templates für den Kategoriebaum.

11. FAQ: Mobile Navigation in Hyvä als Alpine-Komponente

1Warum reicht ein einfaches x-show nicht aus?
Ohne Zustand für Kategorieebenen, Fokus-Trap und Scroll-Lock entsteht ein Barrierefreiheits- und UX-Problem, sobald mehr als eine Ebene existiert.
2Wie modelliert man verschachtelte Kategorieebenen?
Mit einem Stack aus Kategorie-IDs: push beim Tiefer-Navigieren, pop beim Zurück. Die letzte ID bestimmt die aktive Ebene.
3Was ist ein Fokus-Trap?
Ein Mechanismus, der Tab-Navigation innerhalb des geöffneten Panels hält, statt in verdeckten Hintergrundinhalt springen zu lassen.
4Wie schließt man mit Escape?
Mit @keydown.escape.window auf dem Wurzelelement, das closeMenu unabhängig vom Fokus auslöst.
5Warum darf der Hintergrund nicht mitscrollen?
Ohne Lock verliert der Nutzer die Orientierung und iOS Safari zeigt sichtbares Rubber-Banding. $watch auf open steuert overflow-hidden.
6Welche ARIA-Attribute sind Pflicht?
aria-expanded auf dem Button, role=dialog plus aria-modal auf dem Panel, aria-hidden auf dem Hauptinhalt während der Öffnung.
7Warum transform statt left?
transform läuft auf dem Compositor-Thread ohne Reflow. left löst bei jedem Frame ein Layout-Update aus, was auf Mobilgeräten ruckelt.
8Sollte will-change dauerhaft gesetzt sein?
Nein, nur während der aktiven Transition. Dauerhaft gesetzt kostet es Grafikspeicher und kann kontraproduktiv sein.
9Wie rendert man tiefe Kategoriebäume performant?
Kategoriebaum einmalig als JSON serialisieren und mit rekursivem template plus x-for clientseitig durchlaufen, ohne weitere Server-Requests.
10Größter Unterschied zu Luma-jQuery?
Kleinere Bundle-Größe, explizites Zustandsmodell, eingebauter Fokus-Trap und transform-basierte statt jQuery-.animate()-Animation.