L'escenari és gairebé sempre el mateix, que és una taula de dades dins d'un contenidor desplaçable. Cada fila té un menú d'acció, un petit desplegable amb algunes opcions, com ara Edita, Duplica i Suprimeix. El construeixes, sembla que funciona perfectament de manera aïllada, i després algú el posa dins d'aquest div desplaçable i les coses s'esfondran. He vist aquest error exacte en tres bases de codi diferents: el contenidor, la pila i el marc, tots diferents. L'error, però, és totalment idèntic. El menú desplegable es retalla a la vora del contenidor. O apareix darrere del contingut que lògicament hauria d'estar a sota. O funciona bé fins que l'usuari es desplaça i després es desplaça. Arribeu a l'índex z: 9999. De vegades ajuda, però altres vegades no fa absolutament res. Aquesta inconsistència és la primera pista que està passant alguna cosa més profunda. El motiu pel qual segueix tornant és que hi ha tres sistemes de navegador separats i la majoria dels desenvolupadors entenen cadascun per si sol, però mai no pensen en què passa quan tots tres xoquen: desbordament, apilament de contextos i blocs que contenen.
Un cop enteneu com interactuen els tres, els modes de fallada deixen de sentir-se aleatoris. De fet, es tornen previsibles. Les tres coses que realment ho causen Vegem cada un d'aquests elements amb detall. El problema del desbordament Quan configureu desbordament: ocult, desbordament: desplaçament o desbordament: automàtic en un element, el navegador retallarà qualsevol cosa que s'estengui més enllà dels seus límits, inclosos els descendents en posició absoluta. .scroll-container { desbordament: automàtic; alçada: 300px; /* Això retallarà el desplegable, punt final */ }
.menjador desplegable { posició: absoluta; /* No importa -- encara retallat per .scroll-container */ }
Això em va sorprendre la primera vegada que hi vaig trobar. Jo havia assumit la posició: l'absolut deixaria escapar un element del retall d'un contenidor. No ho fa. A la pràctica, això significa que qualsevol avantpassat que tingui un valor de desbordament no visible pot tallar un menú absolutament posicionat, fins i tot si aquest avantpassat no és el bloc que conté el menú. El retall i el posicionament són sistemes separats. Simplement xoquen de maneres que semblen completament aleatòries fins que entengueu tots dos.
Aquí teniu un exemple de React amb createPortal:
importar { createPortal } des de 'react-dom'; importar { useState, useEffect, useRef } de 'reaccionar';
Funció desplegable ({ anchorRef, isOpen, fills }) { const [posició, setPosition] = useState ({ superior: 0, esquerra: 0 });
useEffect(() => { if (isOpen && anchorRef.current) { const rect = anchorRef.current.getBoundingClientRect(); setPosition({ superior: rect.bottom + window.scrollY, esquerra: rect.left + window.scrollX, }); } }, [isOpen, anchorRef]);
if (!isOpen) retorna null;
tornar createPortal(
I, per descomptat, no podem ignorar l'accessibilitat. Els elements fixos que apareixen al contingut encara han de ser accessibles amb el teclat. Si l'ordre d'enfocament no es mou naturalment al menú desplegable fix, haureu de gestionar-lo mitjançant codi. També val la pena comprovar que no s'asseu sobre altres continguts interactius sense cap manera de descartar-lo. Aquest et mossega a les proves de teclat. Posicionament d'àncora CSS: cap a on crec que es dirigeix El posicionament d'ancoratge CSS és la direcció que més m'interessa ara mateix. No estava segur de quina part de l'especificació es podia utilitzar realment quan la vaig mirar per primera vegada. Us permet declarar la relació entre un menú desplegable i el seu activador directament en CSS, i el navegador gestiona les coordenades. .trigger { nom-àncora: --el meu-disparador; }
.menú desplegable { posició: absoluta; posició-àncora: --my-trigger; superior: àncora (inferior); esquerra: àncora(esquerra); position-try-fallbacks: flip-block, flip-inline; }
La propietat position-try-fallbacks és el que fa que valgui la pena utilitzar-lo en un càlcul manual. El navegador prova ubicacions alternatives abans de donar-se per vençut, de manera que un menú desplegable a la part inferior de la finestra gràfica gira automàticament cap amunt en lloc de tallar-se. El suport del navegador és sòlid als navegadors basats en Chromium i creix a Safari. Firefox necessita un polyfill. El paquet @oddbird/css-anchor-positioning cobreix les especificacions bàsiques. He trobat casos de disseny amb ell que requerien alternatives que no m'havia previst, així que tracta-ho com una millora progressiva o combina-ho amb unAlternativa de JavaScript per a Firefox. En resum, prometedor però encara no universal. Prova als teus navegadors de destinació. I pel que fa a l'accessibilitat, declarar una relació visual en CSS no diu res a l'arbre d'accessibilitat. aria-controls, aria-expanded, aria-haspopup — aquesta part encara està a tu. De vegades, la solució és només moure l'element Abans d'arribar a un portal o fer càlculs de coordenades, sempre faig una pregunta primer: aquest menú desplegable necessita realment viure dins del contenidor de desplaçament? Si no ho fa, moure el marcatge a un embolcall de nivell superior elimina completament el problema, sense JavaScript ni càlculs de coordenades. Això no sempre és possible. Si el botó i el menú desplegable estan encapsulats en el mateix component, moure un sense l'altre significa repensar tota l'API. Però quan ho pots fer, no hi ha res a depurar. El problema simplement no existeix. El que el CSS modern encara no soluciona CSS ha recorregut un llarg camí aquí, però encara hi ha llocs on et decep. La posició: els problemes solucionats i transformats encara hi són. Està a les especificacions intencionadament, el que significa que no existeix cap solució alternativa de CSS. Si utilitzeu una biblioteca d'animació que embolcalla el vostre disseny en un element transformat, haureu de tornar a necessitar portals o posicionament d'ancoratge. El posicionament d'ancoratge CSS és prometedor, però nou. Com s'ha esmentat anteriorment, Firefox encara necessita un polyfill en el moment en què escric això. He arribat a casos de disseny amb ell que requerien alternatives que no esperava. Si avui necessiteu un comportament coherent en tots els navegadors, encara busqueu JavaScript per a les parts més complicades. L'addició per la qual he canviat el meu flux de treball és l'API HTML Popover, ara disponible en tots els navegadors moderns. Els elements amb l'atribut popover es mostren a la capa superior del navegador, sobretot, sense necessitat de posicionament de JavaScript.
El maneig d'escapament, l'eliminació al fer clic a l'exterior i la semàntica sòlida d'accessibilitat són gratuïtes per a coses com ara consells sobre eines, ginys de divulgació i superposicions senzilles. És la primera eina a la qual arribo de moment. Dit això, no soluciona el posicionament. Soluciona l'estratificació. Encara necessiteu el posicionament de l'àncora o JavaScript per alinear un popover al seu activador. L'API Popover gestiona les capes. El posicionament de l'àncora s'encarrega de la col·locació. Utilitzats junts, cobreixen la major part del que abans hauríeu d'arribar a una biblioteca. Una guia de decisions per a la vostra situació Després de passar per tot això de la manera més difícil, aquí és com penso realment sobre l'elecció ara.
Utilitzeu un portal. Ho faria servir quan l'activador visqui profundament en contenidors de desplaçament imbricats. Vaig utilitzar aquest patró per als menús d'acció de la taula i el vaig combinar amb la restauració del focus i les comprovacions d'accessibilitat. És l'opció més fiable, però pressuposteu temps per al cablejat addicional. Utilitzeu un posicionament fix. Això és per quan esteu en JavaScript de vainilla o en un marc lleuger i no podeu verificar que cap avantpassat apliqui transformacions o filtres. És senzill de configurar i fàcil de depurar, sempre que es mantingui aquesta restricció. Utilitzeu CSS Anchor Positioning. Aconseguiu-ho quan el vostre navegador ho permeti. Si es requereix compatibilitat amb Firefox, emparell-lo amb el polyfill @oddbird. Aquí és cap a on es dirigeix la plataforma i finalment es convertirà en el vostre enfocament preferit. Reestructura el DOM. Fes-ho servir quan l'arquitectura ho permeti i vols una complexitat d'execució zero. Crec que és probablement l'opció més subestimada. Combina patrons. Fes-ho quan vulguis que el posicionament de l'ancoratge sigui el teu enfocament principal, combinat amb una alternativa de JavaScript per a navegadors no compatibles. O un portal per a la col·locació de DOM emparellat amb getBoundingClientRect() per a la precisió de les coordenades.
Conclusió Acostumava a tractar aquest error com un problema únic: quelcom per corregir i seguir endavant. Però un cop em vaig asseure amb ell el temps suficient per entendre els tres sistemes implicats: retall de desbordament, apilament de contextos i blocs que contenien, va deixar de sentir-se aleatori. Vaig poder mirar un desplegable trencat i rastrejar immediatament quin avantpassat era el responsable. Aquell canvi en la manera de llegir el DOM va ser el veritable resultat. No hi ha una única resposta correcta. El que vaig aconseguir depenia del que pogués controlar a la base de codi: portals quan l'arbre ancestral era impredictible; posicionament fix quan estava net i senzill; moure l'element quan res m'aturava; i posicionament de l'àncora ara,on puc. Sigui el que trieu, no considereu l'accessibilitat com l'últim pas. Segons la meva experiència, és exactament quan es salta. Les relacions ARIA, la gestió de l'enfocament, el comportament del teclat, no són polits. Són part del que fa que la cosa funcioni realment. Consulteu el codi font complet al meu repositori de GitHub. Lectura addicional Aquestes són les referències a les que vaig tornar a seguir mentre treballava amb això:
El context d'apilament (MDN) “CSS Anchor Positioning Guide”, Juan Diego Rodríguez "Com començar amb l'API Popover", Godstime Aburu Interfície d'usuari flotant (floating-ui.com) Desbordament de CSS (MDN)