Scenariusz jest prawie zawsze taki sam, czyli tabela danych wewnątrz przewijalnego kontenera. W każdym wierszu znajduje się menu akcji, małe menu rozwijane z niektórymi opcjami, takimi jak Edytuj, Powiel i Usuń. Budujesz go, wydaje się, że działa doskonale w izolacji, a potem ktoś umieszcza go w tym przewijanym div i wszystko się rozpada. Widziałem dokładnie ten błąd w trzech różnych bazach kodu: kontenerze, stosie i frameworku, wszystkie były inne. Błąd jest jednak całkowicie identyczny.
Lista rozwijana zostaje przycięta na krawędzi kontenera. Lub pojawia się za treścią, która logicznie powinna znajdować się pod nią. Lub działa dobrze, dopóki użytkownik nie przewinie, a następnie dryfuje.
Sięgasz po Z-index: 9999. Czasami pomaga, ale innym razem nie robi zupełnie nic. Ta niespójność jest pierwszą wskazówką, że dzieje się coś głębszego.
Powodem, dla którego wciąż powraca, jest to, że w grę wchodzą trzy oddzielne systemy przeglądarek i większość programistów rozumie każdy z nich osobno, ale nigdy nie myśli o tym, co się stanie, gdy wszystkie trzy się zderzą: przepełnienie, układanie kontekstów i zawieranie bloków.
Gdy zrozumiesz, jak wszystkie trzy współdziałają, tryby awarii przestają wydawać się przypadkowe. Właściwie stają się przewidywalne.
Trzy rzeczy, które faktycznie to powodują
Przyjrzyjmy się szczegółowo każdemu z tych elementów.
Problem przepełnienia
Jeśli ustawisz overflow: hide, overflow: scroll lub overflow: auto na elemencie, przeglądarka przytnie wszystko, co wykracza poza jej granice, łącznie z potomkami umieszczonymi absolutnie.
.scroll-container {
przepełnienie: automatyczne;
wysokość: 300px;
/* Spowoduje to przycięcie listy rozwijanej, kropka */
}
.dropdown {
pozycja: absolutna;
/* Nie ma to znaczenia — nadal obcięte przez .scroll-container */
}
Zdziwiło mnie to, gdy pierwszy raz się z tym zetknąłem. Przyjąłem pozycję: bezwzględna pozwoliłaby elementowi uniknąć przycięcia kontenera. Tak nie jest.
W praktyce oznacza to, że absolutnie pozycjonowane menu może zostać obcięte przez dowolnego przodka, który ma niewidoczną wartość przepełnienia, nawet jeśli ten przodek nie jest blokiem zawierającym menu. Przycinanie i pozycjonowanie to odrębne systemy. Po prostu zdarza się, że zderzają się one w sposób, który wygląda na całkowicie przypadkowy, dopóki nie zrozumiesz obu.
Oto przykład reakcji z użyciem createPortal:
importuj { createPortal } z 'react-dom';
importuj {useState, useEffect, useRef} z „reaguj”;
użyjEfektu(() => {
if (isOpen && AnchorRef.current) {
const rect = kotwicaRef.prąd.getBoundingClientRect();
ustawPozycję({
góra: rect.dtom + window.scrollY,
po lewej: rect.left + window.scrollX,
});
}
}, [isOpen, kotwicaRef]);
if (!isOpen) zwróć wartość null;
zwróć utwórzPortal(
{dzieci}
,
dokument.treść
);
}
I oczywiście nie możemy ignorować dostępności. Naprawione elementy pojawiające się nad treścią muszą być nadal dostępne za pomocą klawiatury. Jeśli kolejność fokusów nie zostanie naturalnie przeniesiona do stałego menu rozwijanego, będziesz musiał zarządzać nią za pomocą kodu. Warto również sprawdzić, czy nie znajduje się on nad innymi interaktywnymi treściami i nie można go odrzucić. Ten gryzie cię w testowaniu klawiatury.
Pozycjonowanie kotwic CSS: dokąd moim zdaniem to zmierza
Pozycjonowanie kotwic CSS to kierunek, który obecnie najbardziej mnie interesuje. Kiedy po raz pierwszy na nią spojrzałem, nie byłem pewien, ile specyfikacji było faktycznie użytecznych. Pozwala zadeklarować relację między listą rozwijaną a jej wyzwalaczem bezpośrednio w CSS, a przeglądarka obsługuje współrzędne.
.wyzwalacz {
nazwa-kotwicy: --my-trigger;
}
.dropdown-menu {
pozycja: absolutna;
kotwica pozycji: --my-trigger;
góra: kotwica (dół);
po lewej: kotwica (po lewej);
próby awaryjne pozycji: flip-block, flip-inline;
}
Właściwość position-try-fallbacks sprawia, że warto z niej korzystać zamiast obliczeń ręcznych. Przeglądarka próbuje alternatywnych rozmieszczeń, zanim się podda, więc lista rozwijana u dołu rzutni automatycznie odwraca się w górę, zamiast zostać obcięta.
Obsługa przeglądarek jest solidna w przeglądarkach opartych na Chromium i rośnie w Safari. Firefox potrzebuje wypełnienia. Pakiet @oddbird/css-anchor-positioning obejmuje podstawową specyfikację. Trafiałem na przypadki brzegowe układu, które wymagały rozwiązań awaryjnych, których się nie spodziewałem, więc traktuj to jako progresywne ulepszenie lub połącz je zZastępczy JavaScript dla Firefoksa.
Krótko mówiąc, obiecujące, ale jeszcze nie uniwersalne. Przetestuj w docelowych przeglądarkach.
A jeśli chodzi o dostępność, zadeklarowanie relacji wizualnej w CSS nie mówi nic drzewu dostępności. aria-controls, aria-expanded, aria-haspopup — ta część nadal należy do ciebie.
Czasami rozwiązaniem jest po prostu przeniesienie elementu
Zanim sięgnę po portal lub dokonam obliczeń współrzędnych, zawsze zadaję najpierw jedno pytanie: Czy to menu rzeczywiście musi znajdować się wewnątrz kontenera przewijania?
Jeśli tak nie jest, przeniesienie znaczników do opakowania wyższego poziomu całkowicie eliminuje problem, bez JavaScript i bez obliczeń współrzędnych.
Nie zawsze jest to możliwe. Jeśli przycisk i menu rozwijane są zawarte w tym samym komponencie, przeniesienie jednego bez drugiego oznacza ponowne przemyślenie całego interfejsu API. Ale kiedy już możesz to zrobić, nie ma co debugować. Problem po prostu nie istnieje.
Czego nowoczesny CSS wciąż nie rozwiązuje
CSS przeszedł długą drogę, ale wciąż są miejsca, w których Cię zawodzi.
Stanowisko: problemy naprawione i związane z transformacją nadal występują. Zostało to celowo uwzględnione w specyfikacji, co oznacza, że nie istnieje żadne obejście CSS. Jeśli używasz biblioteki animacji, która otacza Twój układ przekształconym elementem, znowu potrzebujesz portali lub pozycjonowania kotwic.
Pozycjonowanie kotwic CSS jest obiecujące, ale nowe. Jak wspomniałem wcześniej, w chwili, gdy to piszę, Firefox nadal potrzebuje wypełnienia. Trafiałem na skrajne układy, które wymagały rozwiązań awaryjnych, których się nie spodziewałem. Jeśli obecnie potrzebujesz spójnego zachowania we wszystkich przeglądarkach, w trudnych obszarach nadal sięgasz po JavaScript.
Dodatkiem, dla którego zmieniłem przepływ pracy, jest HTML Popover API, teraz dostępny we wszystkich nowoczesnych przeglądarkach. Elementy z atrybutem popover renderują się w górnej warstwie przeglądarki, ponad wszystkim, bez konieczności pozycjonowania JavaScript.
Treść wyskakującego okienka
Obsługa ucieczki, zamykanie po kliknięciu na zewnątrz i solidna semantyka dostępności są bezpłatne dla takich rzeczy, jak podpowiedzi, widżety ujawniania i proste nakładki. To pierwsze narzędzie, po które sięgam obecnie.
To powiedziawszy, nie rozwiązuje to problemu pozycjonowania. Rozwiązuje nakładanie warstw. Nadal potrzebujesz pozycjonowania kotwicy lub JavaScript, aby dopasować popover do jego wyzwalacza. Interfejs API Popover obsługuje nakładanie warstw. Pozycjonowanie kotwicy obsługuje umieszczanie. Używane razem, obejmują większość zadań, do których wcześniej sięgałeś po bibliotekę.
Przewodnik dotyczący podejmowania decyzji w Twojej sytuacji
Po przejściu przez to wszystko trudnej drogi, oto, jak teraz myślę o tym wyborze.
Użyj portalu. Użyłbym tego, gdy wyzwalacz znajduje się głęboko w zagnieżdżonych kontenerach zwojów. Użyłem tego wzorca w menu akcji tabeli i połączyłem go z przywracaniem fokusu i sprawdzaniem dostępności. Jest to najbardziej niezawodna opcja, ale pozwala zaoszczędzić czas na dodatkowe okablowanie.
Użyj stałego pozycjonowania. Dzieje się tak, gdy korzystasz z prostego JavaScriptu lub lekkiego frameworka i nie możesz sprawdzić, czy żaden przodek nie stosuje transformacji ani filtrów. Jest prosty w konfiguracji i łatwy do debugowania, o ile spełnione jest to jedno ograniczenie.
Użyj pozycjonowania kotwic CSS. Sięgnij po to, jeśli pozwala na to obsługa Twojej przeglądarki. Jeśli wymagana jest obsługa przeglądarki Firefox, sparuj ją z wypełnieniem @oddbird. Platforma ostatecznie zmierza w tym kierunku i ostatecznie stanie się Twoim ulubionym podejściem.
Zrestrukturyzuj model DOM. Użyj tego, jeśli architektura na to pozwala, a chcesz uzyskać zerową złożoność środowiska wykonawczego. Uważam, że jest to prawdopodobnie najbardziej niedoceniana opcja.
Łącz wzorce. Zrób to, jeśli chcesz, aby pozycjonowanie kotwicy było głównym podejściem w połączeniu z rezerwowym JavaScriptem dla nieobsługiwanych przeglądarek. Lub portal do umieszczania DOM w połączeniu z getBoundingClientRect() w celu zapewnienia dokładności współrzędnych.
Wniosek
Kiedyś traktowałem ten błąd jako jednorazowy problem — coś, co można załatać i od czego zacząć. Ale kiedy posiedziałem z nim wystarczająco długo, aby zrozumieć wszystkie trzy systemy, których to dotyczy — obcinanie przepełnienia, układanie kontekstów i zawieranie bloków — przestało to sprawiać wrażenie przypadkowego. Mogłem spojrzeć na zepsutą listę rozwijaną i natychmiast prześledzić, który przodek był za to odpowiedzialny. Ta zmiana w sposobie, w jaki czytam DOM, była prawdziwym wnioskiem.
Nie ma jednej właściwej odpowiedzi. To, po co sięgnąłem, zależało od tego, co mogłem kontrolować w bazie kodu: portale, gdy drzewo przodków było nieprzewidywalne; naprawiono pozycjonowanie, gdy było czyste i proste; przesuwanie elementu, gdy nic mnie nie zatrzymywało; i teraz pozycjonowanie kotwicy,gdzie mogę.
Niezależnie od tego, co ostatecznie wybierzesz, nie traktuj dostępności jako ostatniego kroku. Z mojego doświadczenia wynika, że właśnie wtedy zostaje pominięty. Relacje ARIA, zarządzanie fokusem, zachowanie klawiatury – to nie jest polskie. Są częścią tego, co sprawia, że to coś faktycznie działa.
Sprawdź pełny kod źródłowy w moim repozytorium GitHub.
Dalsze czytanie
Oto odniesienia, do których ciągle wracałem podczas pracy nad tym:
Kontekst układania (MDN)
„Przewodnik po pozycjonowaniu kotwic CSS”, Juan Diego Rodriguez
„Pierwsze kroki z interfejsem API Popover”, Godstime Aburu
Pływający interfejs użytkownika (floating-ui.com)
Przepełnienie CSS (MDN)