Popover Menu Chain
Panneau popover positionné dynamiquement sous son déclencheur — sous-menus enchaînés au survol avec détection de débordement et bascule automatique côté viewport.
Vous aurez accès à la démo interactive avec un compte gratuit.
Cet effet fait partie d'Effect.Labs — 811 effets vanilla, une partie gratuite, une partie premium. Navigation comprend 36 effets, dont 4 gratuits. Découvrir la catégorie →
3 exemples d'usage



Comment ça marche
Au clic sur le bouton déclencheur, le script appelle getBoundingClientRect() sur le bouton et sur son ancêtre .popover-scene pour calculer les coordonnées relatives au conteneur. Il applique directement style.left et style.top sur le panneau — aucun position: sticky ni CSS Anchor Positioning : le calcul est rejoué à chaque ouverture, ce qui garantit l'alignement même si la page a défilé.
Les items portant data-has-sub déclenchent leur sous-panneau au mouseenter : le script ferme d'abord tous les .sub-popover.open déjà actifs (un seul sous-menu ouvert à la fois), puis ajoute la classe open au sous-menu cible via document.getElementById(id).
La détection de débordement s'exécute dans un requestAnimationFrame juste après l'ouverture : si getBoundingClientRect().right du sous-menu dépasse window.innerWidth − 10, la position bascule vers style.left = 'auto' et style.right = 'calc(100% + 6px)' — le sous-menu se replie à gauche du parent.
La fermeture est gérée par un seul écouteur click sur document : si la cible n'appartient ni au panneau ni au déclencheur (contains + comparaison directe), la fonction closeAll retire la classe open du panneau et de tous ses sous-menus. Les transitions CSS (opacity .2s, transform .2s) animent l'apparition et la disparition.
Accessibilité
- prefers-reduced-motion : aucune vérification dans le code — les transitions
opacity .2s, transform .2srestent actives. Complétez avec@media (prefers-reduced-motion: reduce) { .popover-panel, .sub-popover { transition: none } }si nécessaire. - Les items de menu sont des
<button>natifs — focusables au clavier et annoncés par les lecteurs d'écran de base. - ARIA manquant : ni
aria-expandedsur le déclencheur, niaria-haspopup, nirole="menu"/role="menuitem"— à compléter pour une conformité WCAG complète. - Fermeture par Escape non implémentée — les utilisateurs clavier doivent cliquer à l'extérieur du panneau.
- Contraste des items (#c7d2fe sur #1e1b4b) : ratio approximatif 6,5:1 — conforme WCAG AA pour le texte de taille normale.
Compatibilité navigateur
Utilise getBoundingClientRect, classList, requestAnimationFrame et querySelectorAll — supportés partout sans polyfill. Le pattern « Popover API » est simulé en JS pur, sans l'attribut natif popover.
Sans JS, le panneau reste masqué (<code>opacity:0, pointer-events:none</code>) — aucune erreur fatale, le déclencheur reste visible et focusable.
Le code
Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :
<div class="popover-scene" id="popoverScene">
<button class="popover-trigger-btn" id="popoverTrigger">Menu</button>
<div class="popover-panel" id="popoverPanel" style="position:absolute;">
<button class="pop-item" data-has-sub="sub1">Rubrique <span class="arrow">▸</span></button>
<button class="pop-item">Action directe</button>
<div class="sub-popover" id="sub1">
<button class="pop-item">Sous-item 1</button>
<button class="pop-item">Sous-item 2</button>
</div>
</div>
</div>
HTML + CSS + JS complet, prêt à coller — avec des centaines d'effets premium.
Personnaliser
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
| data-has-sub="subId" | absent | Ajoutez ce data-attribute à un .pop-item pour qu'il ouvre le #subId correspondant au survol. Autant de sous-menus que nécessaire. |
| .popover-panel, .sub-popover { background } | #1e1b4b | Couleur de fond du panneau et des sous-menus. Passez à #111827 pour un fond plus neutre ou à #fff pour un thème clair. |
| .popover-panel, .sub-popover { border-color } | rgba(99,102,241,.25) | Bordure du panneau. Utilisez la couleur primaire de votre brand. |
| .pop-item:hover { background } | rgba(99,102,241,.15) | Couleur de survol des items. Adaptez à votre palette (ex. rgba(255,255,255,.08) pour un hover neutre). |
| transition: opacity .2s, transform .2s | .2s | Durée d'animation dans le CSS de .popover-panel et .sub-popover. Passez à .12s pour plus de réactivité ou 0s pour l'instant. |
| gap déclencheur → panneau (JS) | 6 px | Dans le JS : t.style.top = s.bottom - c.top + 6 + 'px' — le 6 est l'écart en pixels entre le bas du bouton et le haut du panneau. |
| gap panneau → sous-menu (CSS) | calc(100% + 6px) | Propriété left de .sub-popover — contrôle le gap horizontal entre le bord droit du panneau parent et le sous-menu. |
| .arrow (▸) | ▸ | Indicateur de sous-menu sur les items. Remplacez par une icône SVG ou un caractère Unicode (ex. ›, →, ▶). |
FAQ
getBoundingClientRect et classList — compatible Safari 14 et Firefox 87 sans polyfill.<button class="pop-item" data-has-sub="monSousMenu">Mon item <span class="arrow">▸</span></button> dans #popoverPanel, puis <div class="sub-popover" id="monSousMenu">…</div> juste après. Le script détecte automatiquement tous les [data-has-sub] via querySelectorAll.click synthétique : document.getElementById('popoverTrigger').click(). Pour fermer, appelez document.getElementById('popoverPanel').classList.remove('open') puis retirez .open de tous les sous-menus avec querySelectorAll('.sub-popover.open').