Scenariot är nästan alltid detsamma, vilket är en datatabell inuti en rullningsbar behållare. Varje rad har en åtgärdsmeny, en liten rullgardinsmeny med några alternativ, som Redigera, Duplicera och Ta bort. Du bygger det, det verkar fungera perfekt isolerat, och sedan lägger någon in det i den rullningsbara div och saker faller isär. Jag har sett denna exakta bugg i tre olika kodbaser: behållaren, stacken och ramverket, alla olika. Buggen är dock helt identisk. Listrutan klipps av vid behållarens kant. Eller så dyker det upp bakom innehåll som logiskt sett borde ligga under det. Eller så fungerar det bra tills användaren scrollar och sedan driver det. Du sträcker dig efter z-index: 9999. Ibland hjälper det, men andra gånger gör det absolut ingenting. Den inkonsekvensen är den första ledtråden om att något djupare händer. Anledningen till att det hela tiden kommer tillbaka är att tre separata webbläsarsystem är inblandade, och de flesta utvecklare förstår var och en på egen hand men tänker aldrig på vad som händer när alla tre kolliderar: översvämning, stapling av sammanhang och innehållande block.
När du väl förstår hur alla tre interagerar slutar fellägena att kännas slumpmässiga. Faktum är att de blir förutsägbara. De tre sakerna som faktiskt orsakar detta Låt oss titta på var och en av dessa artiklar i detalj. Översvämningsproblemet När du ställer in overflow: hidden, overflow: scroll, eller overflow: auto på ett element, kommer webbläsaren att klippa allt som sträcker sig utanför dess gränser, inklusive absolut placerade avkomlingar. .scroll-container { overflow: auto; höjd: 300px; /* Detta kommer att klippa ned rullgardinsmenyn, punkt */ }
.dropdown { position: absolut; /* Spelar ingen roll -- fortfarande klippt av .scroll-container */ }
Det förvånade mig första gången jag stötte på det. Jag hade intagit position: absolut skulle låta ett element undkomma en containers klippning. Det gör det inte. I praktiken betyder det att en absolut placerad meny kan skäras av av vilken förfader som helst som har ett icke-synligt överflödesvärde, även om den förfadern inte är menyns innehållsblock. Klippning och positionering är separata system. De råkar bara kollidera på sätt som ser helt slumpmässiga ut tills du förstår båda.
Här är ett React-exempel med createPortal:
importera { createPortal } från 'react-dom'; importera { useState, useEffect, useRef } från 'react';
function Dropdown({ anchorRef, isOpen, children }) { const [position, setPosition] = useState({ top: 0, left: 0 });
useEffect(() => { if (isOpen && anchorRef.current) { const rect = anchorRef.current.getBoundingClientRect(); setPosition({ top: rect.bottom + window.scrollY, vänster: rect.left + window.scrollX, }); } }, [isOpen, anchorRef]);
if (!isOpen) returnera null;
returnera createPortal(
Och naturligtvis kan vi inte ignorera tillgänglighet. Fasta element som visas över innehåll måste fortfarande vara tillgängliga med tangentbordet. Om fokusordningen inte naturligt flyttas till den fasta rullgardinsmenyn måste du hantera den med kod. Det är också värt att kontrollera att det inte sitter över annat interaktivt innehåll utan något sätt att avfärda det. Den där biter dig i tangentbordstestning. CSS-ankarpositionering: vart jag tror att detta är på väg CSS Anchor Positioning är den riktning jag är mest intresserad av just nu. Jag var inte säker på hur mycket av specen som faktiskt var användbar när jag först tittade på den. Det låter dig deklarera förhållandet mellan en dropdown och dess utlösare direkt i CSS, och webbläsaren hanterar koordinaterna. .trigger { ankarnamn: --min-utlösare; }
.rullgardinsmeny { position: absolut; position-anchor: --min-trigger; topp: ankare(botten); vänster: ankare(vänster); position-try-fallbacks: flip-block, flip-inline; }
Egenskapen position-try-fallbacks är det som gör detta värt att använda över en manuell beräkning. Webbläsaren provar alternativa placeringar innan den ger upp, så en rullgardinsmeny längst ned i visningsporten vänds automatiskt uppåt istället för att bli avskuren. Webbläsarstödet är stabilt i Chromium-baserade webbläsare och växer i Safari. Firefox behöver en polyfill. @oddbird/css-anchor-positioning-paketet täcker kärnspecifikationen. Jag har träffat layout kant fall med det som krävde reservdelar som jag inte förutsåg, så behandla det som en progressiv förbättring eller koppla ihop det med enJavaScript reserv för Firefox. Kort sagt, lovande men inte universellt än. Testa i dina målwebbläsare. Och när det gäller tillgänglighet, säger inte tillgänglighetsträdet någonting att deklarera en visuell relation i CSS. aria-kontroller, aria-expanderade, aria-haspopup — den delen är fortfarande på dig. Ibland är fixen bara att flytta elementet Before reaching for a portal or making coordinate calculations, I always ask one question first: Does this dropdown actually need to live inside the scroll container? Om den inte gör det eliminerar problemet helt och hållet om du flyttar markeringen till ett omslag på högre nivå, utan JavaScript och inga koordinatberäkningar. Detta är inte alltid möjligt. Om knappen och rullgardinsmenyn är inkapslade i samma komponent, innebär det att flytta den ena utan den andra att tänka om hela API:et. Men när du kan göra det finns det inget att felsöka. Problemet finns helt enkelt inte. Vad modern CSS fortfarande inte löser CSS har kommit långt här, men det finns fortfarande ställen där det sviker dig. Positionen: problem med att fixa och omvandla finns kvar. Det är avsiktligt i specifikationen, vilket betyder att det inte finns någon CSS-lösning. Om du använder ett animationsbibliotek som omsluter din layout i ett transformerat element, behöver du tillbaka portaler eller ankarpositionering. CSS Anchor Positioning är lovande, men nytt. Som nämnts tidigare behöver Firefox fortfarande en polyfill när jag skriver detta. Jag har träffat layoutkantfall med det som krävde fallbacks som jag inte förutsåg. Om du behöver konsekvent beteende i alla webbläsare idag, söker du fortfarande efter JavaScript för de knepiga delarna. Tillägget som jag faktiskt har ändrat mitt arbetsflöde för är HTML Popover API, nu tillgängligt i alla moderna webbläsare. Element med popover-attributet återges i webbläsarens översta lager, framför allt, utan att JavaScript-positionering behövs.
Escape handling, dismiss-on-click-outside, and solid accessibility semantics come free for things like tooltips, disclosure widgets, and simple overlays. Det är det första verktyget jag når för närvarande. Som sagt, det löser inte positionering. Det löser skiktning. Du behöver fortfarande ankarpositionering eller JavaScript för att anpassa en popover till dess utlösare. Popover API hanterar skiktningen. Ankarpositionering sköter placeringen. Tillsammans täcker de det mesta du tidigare hade nått för ett bibliotek att göra. En beslutsguide för din situation Efter att ha gått igenom allt detta på den hårda vägen, så här tänker jag faktiskt om valet nu.
Använd en portal. Jag skulle använda den här när utlösaren bor djupt i kapslade rullningsbehållare. Jag använde det här mönstret för tabellåtgärdsmenyer och parade ihop det med fokusåterställning och tillgänglighetskontroller. Det är det mest tillförlitliga alternativet, men budgeterar tid för extra ledningar. Use fixed positioning.This is for when you’re in vanilla JavaScript or a lightweight framework and can verify no ancestor applies transforms or filters. Det är enkelt att ställa in och enkelt att felsöka, så länge den ena begränsningen gäller. Använd CSS Anchor Positioning. Reach för detta när ditt webbläsarstöd tillåter det. Om Firefox-stöd krävs, para ihop det med @oddbird polyfill. Det är dit plattformen i slutändan är på väg och kommer så småningom att bli din bästa strategi. Omstrukturera DOM. Använd detta när arkitekturen tillåter det och du vill ha noll runtime-komplexitet. Jag tror att det förmodligen är det mest underskattade alternativet. Kombinera mönster. Gör det här när du vill ha ankarpositionering som ditt primära tillvägagångssätt, tillsammans med en JavaScript reserv för webbläsare som inte stöds. Eller en portal för DOM-placering parad med getBoundingClientRect() för koordinatnoggrannhet.
Slutsats Jag brukade behandla denna bugg som ett engångsproblem - något att korrigera och gå vidare från. But once I sat with it long enough to understand all three systems involved — overflow clipping, stacking contexts, and containing blocks — it stopped feeling random. Jag kunde titta på en trasig rullgardinsmeny och omedelbart spåra vilken förfader som var ansvarig. Den förändringen i hur jag läste DOM var den verkliga takeaway. Det finns inget enda rätt svar. Vad jag sträckte mig efter berodde på vad jag kunde kontrollera i kodbasen: portaler när förfäderträdet var oförutsägbart; fast positionering när det var rent och enkelt; flytta elementet när ingenting stoppade mig; och ankarpositionering nu,där jag kan. Vad du än väljer, behandla inte tillgänglighet som det sista steget. Enligt min erfarenhet är det precis då det hoppas över. ARIA-relationerna, fokushanteringen, tangentbordsbeteendet - de är inte polska. De är en del av det som gör att saken faktiskt fungerar. Kolla in hela källkoden i min GitHub-repo. Ytterligare läsning Det här är referenserna jag återvände till när jag arbetade igenom detta:
Stacking Context (MDN) "CSS Anchor Positioning Guide", Juan Diego Rodriguez "Komma igång med Popover API", Godstime Aburu Flytande UI (floating-ui.com) CSS Overflow (MDN)