Scenarioet er nesten alltid det samme, som er en datatabell inne i en rullbar beholder. Hver rad har en handlingsmeny, en liten rullegardin med noen alternativer, som Rediger, Dupliser og Slett. Hvis du bygger den, ser den ut til å fungere perfekt isolert, og så legger noen den inn i den rullbare div, og ting faller fra hverandre. Jeg har sett denne nøyaktige feilen i tre forskjellige kodebaser: beholderen, stabelen og rammeverket, alle forskjellige. Feilen er imidlertid helt identisk. Nedtrekkslisten blir klippet ved kanten av beholderen. Eller det dukker opp bak innhold som logisk sett burde være under det. Eller det fungerer fint til brukeren ruller, og så driver det. Du strekker deg etter z-indeks: 9999. Noen ganger hjelper det, men andre ganger gjør det absolutt ingenting. Den inkonsekvensen er den første ledetråden om at noe dypere skjer. Grunnen til at det stadig kommer tilbake er at tre separate nettlesersystemer er involvert, og de fleste utviklere forstår hvert enkelt av seg selv, men tenker aldri på hva som skjer når alle tre kolliderer: overløp, stabling av kontekster og blokker.
Når du forstår hvordan alle tre samhandler, slutter feilmodusene å føles tilfeldige. Faktisk blir de forutsigbare. De tre tingene som faktisk forårsaker dette La oss se på hver av disse elementene i detalj. Overløpsproblemet Når du angir overløp: skjult, overløp: rull eller overløp: auto på et element, vil nettleseren klippe alt som strekker seg utover grensene, inkludert absolutt plasserte etterkommere. .scroll-container { overløp: auto; høyde: 300px; /* Dette vil klippe ned rullegardinmenyen, punktum */ }
.dropdown { posisjon: absolutt; /* Spiller ingen rolle -- fortsatt klippet av .scroll-container */ }
Det overrasket meg første gang jeg traff det. Jeg hadde inntatt posisjon: absolutt ville la et element unnslippe en containers klipping. Det gjør det ikke. I praksis betyr det at en absolutt plassert meny kan kuttes av en hvilken som helst stamfar som har en ikke-synlig overløpsverdi, selv om den stamfaren ikke er menyens blokk. Klipping og posisjonering er separate systemer. De tilfeldigvis kolliderer på måter som ser helt tilfeldige ut til du forstår begge deler.
Her er et React-eksempel ved å bruke createPortal:
importer { createPortal } fra 'react-dom'; importer { useState, useEffect, useRef } fra 'react';
function Dropdown({ anchorRef, isOpen, children }) { const [posisjon, settposisjon] = brukState({ topp: 0, venstre: 0 });
useEffect(() => { if (erOpen && anchorRef.current) { const rect = anchorRef.current.getBoundingClientRect(); setPosition({ topp: rect.bottom + window.scrollY, venstre: rect.left + window.scrollX, }); } }, [isOpen, anchorRef]);
if (!isOpen) returner null;
return createPortal(
Og selvfølgelig kan vi ikke ignorere tilgjengelighet. Faste elementer som vises over innhold, må fortsatt være tilgjengelige med tastaturet. Hvis fokusrekkefølgen ikke naturlig flyttes inn i den faste rullegardinmenyen, må du administrere den ved hjelp av kode. Det er også verdt å sjekke at det ikke ligger over annet interaktivt innhold uten noen måte å avvise det. Den biter deg i tastaturtesting. CSS-ankerposisjonering: Hvor jeg tror dette er på vei CSS Anchor Positioning er retningen jeg er mest interessert i akkurat nå. Jeg var ikke sikker på hvor mye av spesifikasjonen som faktisk var brukbar da jeg først så på den. Den lar deg deklarere forholdet mellom en rullegardin og dens utløser direkte i CSS, og nettleseren håndterer koordinatene. .trigger { ankernavn: --min-utløser; }
.rullegardinmeny { posisjon: absolutt; posisjonsanker: --min-utløser; topp: anker(bunn); venstre: anker(venstre); posisjon-try-fallbacks: flip-block, flip-inline; }
Posisjon-try-fallbacks-egenskapen er det som gjør dette verdt å bruke over en manuell beregning. Nettleseren prøver alternative plasseringer før de gir opp, så en rullegardin nederst i visningsporten vipper automatisk oppover i stedet for å bli avskåret. Nettleserstøtten er solid i Chromium-baserte nettlesere og vokser i Safari. Firefox trenger en polyfill. @oddbird/css-anchor-positioning-pakken dekker kjernespesifikasjonen. Jeg har truffet layout-kantsaker med det som krevde fallbacks jeg ikke forutså, så behandle det som en progressiv forbedring eller par det med enJavaScript fallback for Firefox. Kort sagt, lovende, men ikke universelt ennå. Test i målnettleserne dine. Og når det gjelder tilgjengelighet, forteller ikke tilgjengelighetstreet noe å erklære et visuelt forhold i CSS. aria-kontroller, aria-utvidet, aria-haspopup - den delen er fortsatt på deg. Noen ganger er løsningen bare å flytte elementet Før jeg strekker meg etter en portal eller gjør koordinatberegninger, stiller jeg alltid ett spørsmål først: Trenger denne rullegardinmenyen faktisk være inne i rullebeholderen? Hvis den ikke gjør det, vil flytting av markeringen til en innpakning på høyere nivå eliminere problemet helt, uten JavaScript og ingen koordinatberegninger. Dette er ikke alltid mulig. Hvis knappen og rullegardinmenyen er innkapslet i samme komponent, betyr det å revurdere hele API-en å flytte den ene uten den andre. Men når du kan gjøre det, er det ingenting å feilsøke. Problemet eksisterer bare ikke. Hva moderne CSS fortsatt ikke løser CSS har kommet langt her, men det er fortsatt steder den svikter deg. Stillingen: problemer med å fikse og transformere er der fortsatt. Det er i spesifikasjonen med vilje, noe som betyr at det ikke finnes noen CSS-løsning. Hvis du bruker et animasjonsbibliotek som pakker oppsettet inn i et transformert element, er du tilbake til å trenge portaler eller ankerplassering. CSS Anchor Positioning er lovende, men nytt. Som nevnt tidligere, trenger Firefox fortsatt en polyfill på det tidspunktet jeg skriver dette. Jeg har truffet layoutkantsaker med det som krevde fallbacks jeg ikke forutså. Hvis du trenger konsekvent oppførsel på tvers av alle nettlesere i dag, strekker du deg fortsatt etter JavaScript for de vanskelige delene. Tillegget jeg faktisk har endret arbeidsflyten min for er HTML Popover API, nå tilgjengelig i alle moderne nettlesere. Elementer med popover-attributtet gjengis i nettleserens øverste lag, over alt, uten behov for JavaScript-posisjonering.
Escape-håndtering, avvisning-ved-klikk-utenfor og solid tilgjengelighetssemantikk kommer gratis for ting som verktøytips, avsløringsmoduler og enkle overlegg. Det er det første verktøyet jeg nå for øyeblikket. Når det er sagt, løser det ikke posisjonering. Det løser lagdeling. Du trenger fortsatt ankerposisjonering eller JavaScript for å justere en popover etter utløseren. Popover API håndterer lagdelingen. Ankerposisjonering håndterer plasseringen. Brukt sammen, dekker de det meste av det du tidligere hadde søkt på et bibliotek å gjøre. En beslutningsveiledning for din situasjon Etter å ha gått gjennom alt dette på den harde måten, her er hvordan jeg faktisk tenker på valget nå.
Bruk en portal. Jeg vil bruke denne når utløseren bor dypt i nestede rullebeholdere. Jeg brukte dette mønsteret for tabellhandlingsmenyer og paret det med fokusgjenoppretting og tilgjengelighetskontroller. Det er det mest pålitelige alternativet, men budsjett tid for ekstra ledninger. Bruk fast posisjonering. Dette er for når du bruker vanilje JavaScript eller et lett rammeverk og kan bekrefte at ingen forfedre bruker transformasjoner eller filtre. Det er enkelt å sette opp og enkelt å feilsøke, så lenge den ene begrensningen gjelder. Bruk CSS Anchor Positioning.Reach for dette når nettleserstøtten tillater det. Hvis Firefox-støtte er nødvendig, parer du den med @oddbird polyfill. Det er dit plattformen til syvende og sist er på vei, og vil til slutt bli din foretrukne tilnærming. Omstrukturer DOM. Bruk dette når arkitekturen tillater det, og du vil ha null kjøretidskompleksitet. Jeg tror det sannsynligvis er det mest undervurderte alternativet. Kombiner mønstre. Gjør dette når du vil ha ankerposisjonering som din primære tilnærming, sammen med en JavaScript-reserve for nettlesere som ikke støttes. Eller en portal for DOM-plassering sammenkoblet med getBoundingClientRect() for koordinatnøyaktighet.
Konklusjon Jeg pleide å behandle denne feilen som et engangsproblem - noe å lappe og gå videre fra. Men når jeg satt med den lenge nok til å forstå alle de tre systemene som er involvert – overløpsklipping, stablingskontekster og blokker – sluttet det å føles tilfeldig. Jeg kunne se på en ødelagt rullegardin og umiddelbart spore hvilken stamfar som var ansvarlig. Det skiftet i hvordan jeg leste DOM var den virkelige takeaway. Det finnes ikke et enkelt riktig svar. Hva jeg strakte meg etter var avhengig av hva jeg kunne kontrollere i kodebasen: portaler når forfedretreet var uforutsigbart; fast plassering når det var rent og enkelt; flytte elementet når ingenting stoppet meg; og ankerposisjonering nå,hvor jeg kan. Uansett hva du ender opp med å velge, ikke behandle tilgjengelighet som det siste trinnet. Etter min erfaring er det akkurat da det blir hoppet over. ARIA-relasjonene, fokusstyringen, tastaturoppførselen - de er ikke polske. De er en del av det som gjør at tingen faktisk fungerer. Sjekk ut hele kildekoden i GitHub-repoen min. Videre lesing Dette er referansene jeg stadig kom tilbake til mens jeg jobbet gjennom dette:
The Stacking Context (MDN) "CSS Anchor Positioning Guide", Juan Diego Rodriguez "Kom i gang med Popover API", Godstime Aburu Flytende brukergrensesnitt (floating-ui.com) CSS Overflow (MDN)