Lo scenario è quasi sempre lo stesso, ovvero una tabella di dati all'interno di un contenitore scorrevole. Ogni riga ha un menu di azioni, un piccolo menu a discesa con alcune opzioni, come Modifica, Duplica ed Elimina. Lo costruisci, sembra funzionare perfettamente da solo, poi qualcuno lo inserisce in quel div scorrevole e le cose vanno in pezzi. Ho riscontrato esattamente questo bug in tre diverse basi di codice: il contenitore, lo stack e il framework, tutti diversi. Il bug, però, è totalmente identico. Il menu a discesa viene ritagliato sul bordo del contenitore. Oppure appare dietro contenuti che logicamente dovrebbero trovarsi sotto di esso. Oppure funziona bene finché l'utente non scorre e poi va alla deriva. Raggiungi z-index: 9999. A volte aiuta, ma altre volte non fa assolutamente nulla. Questa incoerenza è il primo indizio che sta accadendo qualcosa di più profondo. Il motivo per cui continua a ripresentarsi è che sono coinvolti tre sistemi browser separati e la maggior parte degli sviluppatori li comprende ciascuno da solo, ma non pensa mai a cosa succede quando tutti e tre entrano in collisione: overflow, contesti di impilamento e blocchi di contenimento.
Una volta compreso come interagiscono tutti e tre, le modalità di fallimento smettono di sembrare casuali. In effetti, diventano prevedibili. Le tre cose che effettivamente causano questo Diamo un'occhiata a ciascuno di questi elementi in dettaglio. Il problema del traboccamento Quando imposti overflow: hidden, overflow: scroll o overflow: auto su un elemento, il browser ritaglia tutto ciò che si estende oltre i suoi limiti, inclusi i discendenti posizionati in modo assoluto. .contenitore di scorrimento { overflow: automatico; altezza: 300px; /* Questo taglierà il menu a discesa, punto */ }
.a discesa { posizione: assoluta; /* Non importa: ancora ritagliato da .scroll-container */ }
Ciò mi ha sorpreso la prima volta che l'ho incontrato. Avevo assunto la posizione: assoluto avrebbe permesso a un elemento di sfuggire al ritaglio di un contenitore. Non è così. In pratica, ciò significa che un menu posizionato in modo assoluto può essere tagliato da qualsiasi antenato che abbia un valore di overflow non visibile, anche se quell’antenato non è il blocco contenitore del menu. Il ritaglio e il posizionamento sono sistemi separati. Capita semplicemente che si scontrino in modi che sembrano completamente casuali finché non li capisci entrambi.
Ecco un esempio di React utilizzando createPortal:
importa {creaPortal} da 'react-dom'; import {useState, useEffect, useRef } da 'react';
funzione Dropdown({ AnchorRef, isOpen, children }) { const [posizione, setPosizione] = useState({ alto: 0, sinistra: 0 });
useEffect(() => { if (isOpen && AnchorRef.current) { const rect = AnchorRef.current.getBoundingClientRect(); setPosizione({ in alto: rect.bottom + window.scrollY, sinistra: rect.left + window.scrollX, }); } }, [isOpen, AnchorRef]);
if (!isOpen) restituisce null;
return creaPortale(
E, naturalmente, non possiamo ignorare l’accessibilità. Gli elementi fissi visualizzati sul contenuto devono essere comunque raggiungibili dalla tastiera. Se l'ordine di messa a fuoco non si sposta naturalmente nel menu a discesa fisso, dovrai gestirlo utilizzando il codice. Vale anche la pena verificare che non si sovrapponga ad altri contenuti interattivi senza alcun modo per chiuderlo. Quello ti morde nei test della tastiera. Posizionamento dell'ancora CSS: dove penso che stiamo andando Il posizionamento dell'ancora CSS è la direzione che mi interessa di più in questo momento. Non ero sicuro di quante specifiche fossero effettivamente utilizzabili quando l'ho guardato per la prima volta. Ti consente di dichiarare la relazione tra un menu a discesa e il suo trigger direttamente nei CSS e il browser gestisce le coordinate. .trigger { nome-ancora: --my-trigger; }
.menu a discesa { posizione: assoluta; posizione-ancora: --my-trigger; in alto: ancora(in basso); sinistra: ancora(sinistra); posizione-prova-fallback: flip-block, flip-inline; }
La proprietà position-try-fallback è ciò che rende utile utilizzarla rispetto a un calcolo manuale. Il browser prova posizionamenti alternativi prima di arrendersi, quindi un menu a discesa nella parte inferiore del viewport si ribalta automaticamente verso l'alto invece di essere tagliato. Il supporto dei browser è solido nei browser basati su Chromium e cresce in Safari. Firefox ha bisogno di un polyfill. Il pacchetto @oddbird/css-anchor-position copre le specifiche principali. Con esso ho riscontrato casi limite di layout che richiedevano fallback che non avevo previsto, quindi trattalo come un miglioramento progressivo o abbinalo a unRipiego JavaScript per Firefox. Insomma, promettente ma non ancora universale. Prova nei browser di destinazione. E per quanto riguarda l’accessibilità, dichiarare una relazione visiva nei CSS non dice nulla all’albero dell’accessibilità. aria-controls, aria-expanded, aria-haspopup: quella parte è ancora a carico tuo. A volte la soluzione è semplicemente spostare l'elemento Prima di raggiungere un portale o effettuare calcoli sulle coordinate, faccio sempre prima una domanda: questo menu a discesa deve effettivamente vivere all'interno del contenitore di scorrimento? In caso contrario, lo spostamento del markup su un wrapper di livello superiore elimina completamente il problema, senza JavaScript e senza calcoli delle coordinate. Questo non è sempre possibile. Se il pulsante e il menu a discesa sono incapsulati nello stesso componente, spostarne uno senza l'altro significa ripensare l'intera API. Ma quando puoi farlo, non c’è nulla di cui eseguire il debug. Il problema semplicemente non esiste. Ciò che i CSS moderni ancora non risolvono I CSS hanno fatto molta strada qui, ma ci sono ancora posti in cui ti delude. La posizione: i problemi risolti e di trasformazione sono ancora presenti. È intenzionalmente nelle specifiche, il che significa che non esiste alcuna soluzione alternativa CSS. Se stai utilizzando una libreria di animazioni che avvolge il tuo layout in un elemento trasformato, torni ad aver bisogno di portali o di posizionamento di ancoraggi. Il posizionamento dell'ancora CSS è promettente, ma nuovo. Come accennato in precedenza, Firefox necessita ancora di un polyfill nel momento in cui scrivo questo. Ho riscontrato casi limite di layout che richiedevano fallback che non avevo previsto. Se oggi hai bisogno di un comportamento coerente su tutti i browser, stai ancora cercando JavaScript per le parti difficili. L'aggiunta per cui ho effettivamente modificato il mio flusso di lavoro è l'API HTML Popover, ora disponibile in tutti i browser moderni. Gli elementi con l'attributo popover vengono visualizzati nel livello superiore del browser, sopra ogni cosa, senza bisogno del posizionamento JavaScript.
La gestione dell'escape, l'eliminazione con un clic all'esterno e una solida semantica di accessibilità sono gratuiti per elementi come descrizioni comandi, widget di divulgazione e semplici sovrapposizioni. È il primo strumento che raggiungo per ora. Detto questo, non risolve il posizionamento. Risolve la stratificazione. Hai ancora bisogno del posizionamento dell'ancora o di JavaScript per allineare un popover al suo trigger. L'API Popover gestisce la stratificazione. Il posizionamento dell'ancoraggio gestisce il posizionamento. Usati insieme, coprono la maggior parte di ciò che in precedenza avresti raggiunto per una biblioteca. Una guida decisionale per la tua situazione Dopo aver affrontato tutto questo nel modo più duro, ecco come penso effettivamente alla scelta adesso.
Usa un portale. Lo userei quando il trigger si trova in profondità nei contenitori di scorrimento nidificati. Ho utilizzato questo modello per i menu di azione della tabella e l'ho abbinato al ripristino dello stato attivo e ai controlli di accessibilità. È l’opzione più affidabile, ma prevede tempo per il cablaggio aggiuntivo. Utilizza il posizionamento fisso. Questo è utile quando utilizzi JavaScript Vanilla o un framework leggero e puoi verificare che nessun antenato applichi trasformazioni o filtri. È semplice da configurare e semplice da eseguire il debug, purché rimanga valido questo vincolo. Utilizza CSS Anchor Positioning.Reach per questo quando il supporto del tuo browser lo consente. Se è richiesto il supporto per Firefox, abbinalo al polyfill @oddbird. È qui che la piattaforma si sta dirigendo alla fine e alla fine diventerà il tuo approccio preferito. Ristruttura il DOM. Utilizzalo quando l'architettura lo consente e desideri zero complessità di runtime. Credo che sia probabilmente l’opzione più sottovalutata. Combina modelli. Fallo quando desideri che il posizionamento dell'ancora sia il tuo approccio principale, abbinato a un fallback JavaScript per i browser non supportati. Oppure un portale per il posizionamento DOM abbinato a getBoundingClientRect() per la precisione delle coordinate.
Conclusione Trattavo questo bug come un problema una tantum, qualcosa su cui correggere e da cui andare avanti. Ma una volta che ci sono rimasto abbastanza a lungo da comprendere tutti e tre i sistemi coinvolti - ritaglio dell'overflow, contesti di impilamento e blocchi di contenimento - ha smesso di sembrare casuale. Potrei guardare un menu a discesa rotto e risalire immediatamente all'antenato responsabile. Quel cambiamento nel modo in cui leggo il DOM è stato il vero punto di partenza. Non esiste un’unica risposta giusta. Ciò che ho raggiunto dipendeva da ciò che potevo controllare nel codice base: portali quando l'albero degli antenati era imprevedibile; posizionamento fisso quando era pulito e semplice; spostare l'elemento quando nulla mi fermava; e il posizionamento dell'ancora ora,dove posso. Qualunque cosa tu scelga, non considerare l’accessibilità come l’ultimo passo. Nella mia esperienza, è esattamente quando viene saltato. Le relazioni ARIA, la gestione del focus, il comportamento della tastiera: non sono perfetti. Fanno parte di ciò che fa sì che le cose funzionino davvero. Controlla il codice sorgente completo nel mio repository GitHub. Ulteriori letture Questi sono i riferimenti a cui continuavo a tornare mentre lavoravo a questo:
Il contesto dello stacking (MDN) "Guida al posizionamento dell'ancora CSS", Juan Diego Rodriguez "Come iniziare con l'API Popover", Godstime Aburu Interfaccia utente mobile (floating-ui.com) Overflow CSS (MDN)