Zastępowanie bibliotek tooltipów API Popover oraz pozycjonowaniem elementów CSS
Dlaczego potrzebne są podpowiedzi, Popper i Floating UI – funkcje wbudowane, które obecnie rozwiązują problemy z układaniem elementów, pozycjonowaniem i usuwaniem ich – oraz kiedy biblioteka JavaScript nadal jest przydatna.
Pokazanie linijki tekstu obok przycisku wydaje się błahostką, a mimo to istnieją Popper.js, Floating UI oraz mnóstwo pakietów pomocniczych – ponieważ nigdy wcześniej tak nie było. Podpowiedź to w rzeczywistości trzy odrębne problemy ułożone jedno na drugim, a do niedawna platforma nie oferowała żadnego deklaratywnego rozwiązania dla żadnego z nich. Ten przewodnik analizuje te problemy, pokazuje, ile pamięci zajmuje każdy z nich w gotowym kodzie JavaScript oraz łączy je z dwiema funkcjami przeglądarki, które obecnie radzą sobie z najczęściej występującymi przypadkami: API Popover oraz CSS Anchor Positioning. Pod koniec dowiesz się, które części biblioteki podpowiedzi można usunąć, a które nadal są potrzebne.
Trzy problemy ukryte w jednej podpowiedzi
Każdy z nich stanowi długo istniejącą, dobrze znaną ograniczenie w zachowaniu przeglądarki przed pojawieniem się nowych specyfikacji:
- Kładzenie się elementów. Czy podpowiedź będzie widoczna nad wszystkim innym, czy też ją obetnie wartość
overflow: hiddenjakiegoś elementu nadrzędnego? - Pozycjonowanie. Czy podpowiedź wie, gdzie na ekranie znajduje się element wyzwalający, i czy podąża za tą pozycją podczas przewijania i zmiany rozmiaru?
- Zamykanie. Czy zamyka się sama, gdy użytkownik kliknie gdzie indziej lub naciśnie klawisz Escape?
Przez większość historii internetu każdy projekt, który potrzebował podpowiedzi, menu rozwijanego lub listy autodopasowywania, rozwiązywał wszystkie te trzy problemy za pomocą JavaScripta. Rozwiązania były różne dla każdego przypadku, a traktowanie ich jako jednego problemu doprowadziło do tego, że mała szczegółowość interfejsu stała się zależnością. Dlatego warto przyjrzeć im się po kolei.
Kładzenie się elementów: dlaczego z-index nie może wyjść poza swój kontekst
Każdy element należy do kontekstu układu, który decyduje o tym, co zostanie narysowane na wierzchu czego. z-index określa kolejność elementów w obrębie jednego kontekstu układu; nie może on wydobyć elementu poza kontekst, do którego należy.
Umieść podpowiedź w kontenerze z atrybutem overflow: hidden lub w modale, które tworzy własny kontekst układu – żadna wartość, nawet 999999, nie sprawi, że pojawi się ona powyżej tej granicy. Podpowiedź jest ograniczona regułami renderowania swoich przodków, a z-index nie ma żadnego wpływu poza nimi.
Tradycyjnym wyjściem awaryjnym był portal: konieczne było umieszczenie kodu tooltipa w innym miejscu dokumentu, zazwyczaj na końcu tagu <body>, aby nie odziedziczał on ograniczeń i zasady układania się elementu nadrzędnego. Funkcja createPortal w React istnieje głównie z tego powodu. Jest to raczej rozwiązanie awaryjne na problem, którego CSS nie potrafiło rozwiązać, niż funkcja specyficzna dla React.
Pozycjonowanie: pozycjonowanie absolutne rozumie tylko elementy nadrzędne
position: absolute umieszcza element względem jego najbliższego elementu nadrzędnego o określonej pozycji, czyli najbliższego elementu w drzewie DOM, którego position ma wartość relative, absolute, fixed lub sticky. Kluczowym pojęciem jest tu „element nadrzędny” – odniesienie musi znajdować się w tej samej gałęzi DOM, gdzieś powyżej tooltipa.
Gdy tylko komunikat pomocniczy i element wyzwalający stają się braćmi, albo komunikat pomocniczy jest przenoszony do <body> w celu uniknięcia problemów z układem elementów, element wyzwalający przestaje być przodkiem. CSS nie posiadało sposobu na określenie „umieść to w odniesieniu do tamtego niepowiązanego elementu”. Słabością nie była sama metoda pozycjonowania w CSS, lecz brak jakiejkolwiek relacji pozycjonowania, która ignorowałaby strukturę dokumentu.
Biblioteki wypełniły tę lukę poprzez pomiar. Wykorzystują funkcję getBoundingClientRect(), aby uzyskać ramki elementu wyzwalającego względem obszaru widoku, obliczyć współrzędne komunikatu pomocniczego i powtarzać te obliczenia przy każdym przewijaniu lub zmianie rozmiaru, ponieważ wartości ciągle się zmieniają. Ten nieustannie działający cykl pomiaru i umieszczania stanowi główną funkcję biblioteki do pozycjonowania w czasie wykonywania.
Zamknięcie: zachowanie, którego nie można było opisać za pomocą znaczników
Zanim pojawiła się API Popover, HTML i CSS nie miały możliwości „zamknięcia się po kliknięciu użytkownika poza elementem lub naciśnięciu klawisza Escape”. Wszystko to było realizowane za pomocą skryptów: słuchacz zdarzeń kliknięcia na document, który sprawdzał, czy cel zdarzenia znajduje się poza elementem tooltip, słuchacz zdarzeń keydown czekający na naciśnięcie klawisza Escape, oraz mechanizmy czyszczenia obu tych elementów po rozmontowaniu komponentu, aby uniknąć wycieków zasobów. W odróżnieniu od pierwszych dwóch problemów, ten nie ma żadnego aspektu geometrycznego – jest to czysto behawioralne rozwiązanie, ale nadal stanowi trzeci element kodu w czasie wykonywania, którego przeglądarka nie zapewniała.
Popovery a modale
Pomaga to ustalić terminologię przed przyjrzeniem się składni. Popover to cokolwiek, co jest wyświetlane powyżej reszty strony, pozycjonowane względem elementu wyzwalającego i znikające po kliknięciu poza nim lub naciśnięciu klawisza Escape. Podpowiedzi, menu rozwijane, listy autodopasowywania i menu kontekstowe to wszystko popovers o różnym stylu, ale z tymi samymi trzema problemami technicznymi.
Modal ma ten sam problem z układem elementów na stronie, ale nie jest popoverem, ponieważ blokuje dostęp do innych elementów. Gdy modal jest otwarty, treść za nim pozostaje nieruchoma: użytkownicy nie mogą na nią przenieść kursora, w nią kliknąć ani przewinąć się po niej, a zazwyczaj istnieje tło, na którym skupienie pozostaje wewnątrz modala aż do jego zamknięcia. Pomyśl o prośbie „potwierdź usunięcie”: nic innego na stronie nie jest dostępne, dopóki nie zostanie ona odpowiedziana. Popover niczego nie blokuje – strona pozostaje w pełni interaktywna, a popover po prostu zamyka się, gdy użytkownik przechodzi dalej.
Specyfikacje jasno określają tę różnicę:
popover="auto"umożliwia łatwe zamknięcie bez blokowania: zamyka się po kliknięciu poza nim lub naciśnięciu klawisza Escape i pozostawia fokus wolny. Do tej kategorii należą podpowiedzi i menu rozwijane.popover="manual"pozostaje otwarty dopóki skrypt go nie zamknie, bez możliwości łatwego zamknięcia, co nadaje się na przykład do trwałych powiadomień typu toast.<dialog>otwarty za pomocą.showModal()to wariant blokujący: znajduje się na najwyższym poziomie, ma tło, przejmuje fokus i sprawia, że wszystko za nim staje się nieruchome.- Otwarcie tego
<dialog>za pomocą.show()daje element nierozrywający i zachowuje się podobnie do popovera.
Koszty podejścia opartego na JavaScript
Poniższe rozmiary po stłumieniu pochodzą z npm i obejmują tylko same biblioteki:
popper.js (v1, now deprecated) 7.1 KB
Tippy.js (bundles @popperjs/core) 14.1 KB
Floating UI, vanilla (@floating-ui/dom) 8.1 KB
Floating UI, React bindings 30.1 KB
react-tooltip (@floating-ui/dom + clsx) 14.1 KB
Te liczby nie obejmują niczego, co dodajesz na górze: konfiguracji, komponentu otaczającego, CSS dla strzałek i tematów. To jest podstawowa cena przed wdrożeniem jakiejkolwiek własnej logiki, a projekt łączący na przykład pakiet z podpowiedziami z oddzielnym pakietem rozwijanego menu płaci za to dwa razy.
Żadne z tego nie wynika z słabej inżynierii. W szczególności interfejs typu floating UI jest starannie zaprojektowany; jego głównym zadaniem jest prawidłowe działanie przy przepełnieniu w każdym przeglądarce. Koszt wynikał z konieczności rozwiązania trzech niepowiązanych ze sobą problemów w JavaScriptu, ponieważ platforma nie oferowała niczego innego.
API Popover zajmuje się układaniem i zamykaniem
Dwie oddzielne specyfikacje zastąpiły tę bibliotekę i nie dzielą obowiązków w sposób, jakiego można by się spodziewać. Atrybut popover zajmuje się jednocześnie układaniem i zamykaniem, przy użyciu praktycznie żadnego kodu skryptowego.
<button popovertarget="my-tooltip">Hover me</button>
<div id="my-tooltip" popover="auto">
This is the tooltip content.
</div>
Atrybut popovertarget łączy przycisk z elementem o odpowiadającej mu wartości id. Przy użyciu popover="auto" element jest przenoszony na najwyższy poziom w przeglądarce podczas otwierania się pokrywy, co stanowi dokładnie ucieczkę od zasady overflow: hidden oraz kontekstów układu opisanych wcześniej, a ponadto umożliwia bezpłatne łatwe zamknięcie: kliknięcia poza obszarem pokrywy oraz klawisz Escape zamykają ją bez konieczności stosowania kodu obsługi wydarzeń.
Jedna korekta w sformułowaniu opisu: popovertarget aktywuje się po kliknięciu, dotknięciu lub naciśnięciu klawisza, a nie po przeniesieniu kursora nad element. Prawdziwa podpowiedź pojawiająca się przy przeniesieniu kursora nad element nadal wymaga niewielkiej ilości kodu, aby wywołać funkcje showPopover() i hidePopover() w zdarzeniach dotyczących kursora i fokusa, albo nowszego deklaratywnego mechanizmu, gdy tylko wspierane będą przez docelowe przeglądarki. Mimo to do układania elementów warstwowo i ich usuwania nie jest już potrzebna żadna biblioteka. Pozostaje jeszcze kwestia pozycjonowania, która należy do innego standardu.
Pozycjonowanie elementów za pomocą anchorów
CSS Anchor Positioning rozwiązuje jedyny problem, który pozostawia API Popover. Umożliwia on dowolnym dwóm elementom w dokumencie odwoływanie się do siebie za pomocą nazwy, zamiast poprzez strukturę rodzica i dziecka.
.trigger {
anchor-name: --my-anchor;
}
.tooltip {
position: absolute;
position-anchor: --my-anchor;
top: anchor(--my-anchor bottom);
left: anchor(--my-anchor left);
}
anchor-name rejestruje wyzwalacz pod kreskowanym identyfikatorem, tą samą składnią co używają własne właściwości. position-anchor w podpowiedzi wskazuje na ten nazwę, a funkcja anchor() odczytuje określony brzeg ankeru (top, right, bottom, left lub center), aby podpowiedź mogła się do niego wyrównać.
Kluczowe jest to, że żaden z elementów nie musi zawierać drugiego. Przeglądarka teraz natywnie wykonywa to, co wcześniej getBoundingClientRect() musiało obliczać ręcznie przy każdym przewinięciu. Łącząc to z elementem popover, pamiętaj, że styl arkusza użytkownika nadaje elementom [popover] wartości inset: 0 i margin: auto, aby je usytuować centralnie; jeśli podpowiedź ignoruje twoje odchylenia ankeru, zwykle rozwiązaniem jest przywrócenie tych właściwości.
Aktywowanie mechanizmu obsługi przekroczenia zasięgu bez słuchacza przewijania
Częścią biblioteki do pozycjonowania, która zawiera większość logiki, jest obsługa przekroczenia zasięgu: wykrywanie sytuacji, gdy podpowiedź jest na skraju obszaru widoku, i wybieranie wcześniej innego miejsca umieszczenia. Pozycjonowanie typu anchor obejmuje to za pomocą opcji position-try-fallbacks.
.tooltip {
position: absolute;
position-anchor: --my-anchor;
position-area: top center;
position-try-fallbacks: flip-block, flip-inline;
}
Tutaj position-area: top center ustala domyślne miejsce umieszczenia, a position-try-fallbacks wymienia alternatywy, które przeglądarka sprawdza po kolei, gdy to miejsce spowodowałoby przekroczenie zasięgu bloku, w którym znajduje się podpowiedź, lub samego obszaru widoku. Funkcja flip-block odbija podpowiedź względem osi bloku, dzięki czemu górna strona staje się dolną, natomiast flip-inline odbija ją względem osi liniowej, tak że lewa strona staje się prawą. Przeglądarka ponownie ocenia tę sytuację podczas układania elementów, bez żadnego mechanizmu obsługi przewijania ani skryptu na wątku głównym, który wykrywałby przekroczenie zasięgu.
Gdy zwykłe lustrzane ułożenie nie wystarcza, reguła @position-try umożliwia zdefiniowanie nazwanych alternatywnych pozycji, które stanowią małe bloki deklaracji pozycjonowania, przez które przeglądarka może przechodzić kolejno.
@position-try --below {
position-area: bottom center;
margin-top: 8px;
}
@position-try --above {
position-area: top center;
margin-bottom: 8px;
}
.tooltip {
position-anchor: --my-anchor;
position-try-fallbacks: --above, --below;
}
To jest ta sama decyzja, którą podejmuje Floating UI w JavaScript przy każdym zdarzeniu przewijania – deklaruje ją z góry jako dane, które engine układu analizuje. Należy zauważyć, że reguła .tooltip w tym fragmencie zakłada, iż element jest już ułożony absolutnie lub w sposób fixed, tak jak w wcześniejszych przykładach; pozycjonowanie typu anchor nie ma wpływu na elementy ułożone statycznie.
Kiedy biblioteka do pozycjonowania jest nadal potrzebna
Dla podpowiedzi, prostego menu rozwijanego lub listy autodopasowywania, naturalna kombinacja funkcji systemu operacyjnego stanowi teraz rozsądny standard. Rola biblioteki zmniejszyła się, choć nie zniknęła całkowicie.
Pierwszym ograniczeniem jest obsługa przeglądarek. Przeglądarki Chromium obsługują pozycjonowanie elementów anchor od wersji 125, a @position-try osiągnął poziom Baseline później niż sam anchor(). Obsługa w Safari i Firefox pojawiła się później, a dane podawane w internecie różnią się, dlatego lepiej sprawdzić aktualną tabelę kompatybilności zamiast polegać na jakiejś pojedynczej liście wersji. Gdy obsługa jest brakująca, nie ma łagodnego rozwiązania opartego wyłącznie na CSS: przeglądarka, która nie rozumie anchor(), po prostu nie potrafi poprawnie pozycjonować elementu. Jeśli musisz obsługiwać starsze wersje Safari lub przeglądarki mobilne z starszymi silnikami, miej zapasową opcję; nasz przewodnik po bezpiecznym wdrażaniu nowoczesnego CSS omawia wykrywanie funkcji oraz stopniowe ulepszanie elementów anchor.
Drugi przypadek to zasady układu, które wykraczają poza prostą lustrzane odbicie przy przepełnieniu: panel unoszący zawierający zlistowanie wirtualizowane, sprawdzanie kolizji względem kilku granic jednocześnie lub układ determinowany danymi aplikacji, a nie strukturą. Skrypt może reagować na dowolny stan aplikacji, podczas gdy stała lista awaryjna w CSS zna tylko informacje o strukturze.
W przypadku zwykłym, który obejmuje większość projektów, trudno uzasadnić wykorzystanie od 8 do 30 KB JavaScriptu do zadań, które przeglądarka wykonywa już sama.
Główne wnioski
- Podpowiedź wiąże się z trzema problemami: układem warstw, pozycjonowaniem i zamknięciem. Biblioteki powstały, ponieważ wszystkie te problemy musiały być rozwiązane w skrypcie.
popover="manual" oraz w połączeniu z .showModal() obsługują przypadki trwałego pokazywania i blokowania interfejsu.position-try-fallbacks wraz z @position-try zastępują logikę obsługi przekroczenia granic obszaru, która dominowała w bibliotekach przez długi czas.Literatura pokrewna
- Natywne API przeglądarki zastępujące popularne pakiety npm w 2026 — Wyjaśnia, w jaki sposób natywne funkcje JavaScript i CSS, takie jak Signals, operator pipeline, Temporal oraz Anchor Positioning, zastępują powszechnie używane pakiety npm.
- Bezpieczne wdrażanie nowoczesnego CSS: anchor, grid-lanes, scope i light-dark() — Dowiedz się, jak anchor positioning, popover, grid-lanes masonry, view transitions, light-dark() oraz @scope zastępują biblioteki JavaScript, oraz jak wprowadzić każdy z tych elementów z rozwiązaniami awaryjnymi.