Heti 80 millió letöltés egy tooltip miatt: megérkezett a CSS anchor positioning

A @floating-ui/dom-ot hetente 80 milliószor töltjük le, hogy tooltipeket és dropdownokat pozicionáljunk JavaScriptből. A Firefox 147 óta mindhárom nagy böngésző tudja ugyanezt natív CSS-ből: anchor positioning, automatikus átfordulással a viewport szélén, görgetéskövetéssel, top layerrel. Megnéztem, mit tud, és hol vannak a csapdái.

Van egy npm-csomag, amit hetente több mint 80 milliószor töltünk le azért, hogy ki tudjuk számolni: egy tooltip a gomb fölé vagy alá kerüljön-e. A @floating-ui/dom – a Popper.js utódja – az augusztus közepi héten 81,7 millió letöltésnél járt az npm statisztikája szerint, és akkor a rá épülő komponenskönyvtárakat még nem is számoltuk. Mindez egy olyan feladatra, ami definíció szerint layout: „tedd ezt a dobozt ahhoz a dobozhoz képest ide”. A layout pedig elvileg a böngésző munkaköri leírásának első sora.

Idén januárban, a Firefox 147-tel ez a történet lezárult: a CSS anchor positioning mindhárom nagy böngészőmotorban ott van. Nem kísérleti játékszer – a Chrome 2024 májusa óta szállítja, a Safari tavaly szeptember óta –, úgyhogy ideje megnézni, mit vettünk eddig heti 80 millió letöltésért.

Diagram a CSS anchor positioningról: bal oldalon egy gombhoz rögzített tooltip a 3×3-as position-area rács felső cellájában, jobb oldalon a viewport szélénél a fallback az áthúzott, kilógó pozíció helyett a gomb alá fordítja a buborékot

Miért kellett ehhez húsz évig JavaScript?

A position: absolute a containing blockhoz képest pozicionál, és ezen a láncon kívülre nem lát. A dropdownod viszont szinte sosem maradhat ott, ahol logikusan élne, a gomb mellett a DOM-ban: az útba eső overflow: hidden levágja, egy transform-os ős új containing blockot ránt alá, a z-index-háborúkról nem is beszélve. Ezért portalozzuk a body végére, újabban pedig a top layerbe tesszük popoverként vagy dialogként – a top layerről írtam korábban. Csakhogy onnan a gomb, amihez képest a helyét keresné, CSS-eszközökkel elérhetetlen volt: más a viszonyítási rendszere, és pont.

Maradt a getBoundingClientRect(), plusz scroll- és resize-listenerek, plusz újraszámolás mindenre, ami mozoghat. A Floating UI lényegében ezt csomagolja be (autoUpdate), és ad rá middleware-eket: offset, flip, shift. Kiforrott, korrekt munka – csak épp egy layoutfeladatot old meg a layoutmotoron kívülről, annak minden költségével együtt.

Név, kötés, hely

Az alapszereposztás három tulajdonság. Az anchor kap egy nevet, a pozicionált elem hivatkozik rá, aztán megmondod, hova kerüljön:

.mentes-gomb {
  anchor-name: --mentes;
}

.tooltip {
  position: fixed;
  position-anchor: --mentes;
  position-area: top;
  margin-bottom: 8px;
}

Az anchor-name ugyanolyan dashed ident, mint a custom propertyk neve. A position-area pedig egy 3×3-as rácsban gondolkodik: az anchor a középső cella, a pozicionált elem a maradék nyolc cella (és a span-* kombinációk) bármelyikébe kérhető. A top itt azt jelenti: a felső sáv, az anchorhoz igazítva.

És itt jön az első mondat, amit JS-ből sosem kaptál ingyen: görgetésnél, átméretezésnél, layoutváltozásnál a böngésző magától viszi a tooltipet az anchorral együtt. Nincs listener, nincs autoUpdate, nincs elkésett frame, amiben a menü még a gomb régi helyén lebeg.

Ha a rács kevés, ott az anchor() függvény, amivel inset-tulajdonságokban hivatkozol az anchor széleire, és az anchor-size(), amivel a méretére:

.dropdown {
  position: fixed;
  position-anchor: --mentes;
  top: calc(anchor(bottom) + 8px);
  justify-self: anchor-center;   /* vízszintesen az anchor közepén */
  min-width: anchor-size(width); /* legalább olyan széles, mint a gomb */
}

A viewport széle – amiért a JS-t valójában fizettük

Az igazi munka sosem az volt, hogy a tooltip a gomb fölé kerüljön. Hanem az, hogy mi történjen, amikor a gomb a képernyő tetején van, és fölötte nincs hely. A Floating UI-ban ez a flip és a shift; CSS-ben a position-try-fallbacks:

.tooltip {
  position: fixed;
  position-anchor: --mentes;
  position-area: top;
  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
}

A böngésző sorban végigpróbálja az opciókat, és az elsőt alkalmazza, amelyikkel az elem nem lóg ki a viewportból; ha egyik sem jó, marad az eredeti pozíció. A flip-block a blokktengely mentén tükröz (fölülről alulra), a flip-inline vízszintesen, a kettő együtt átlósan. Ha ez kevés, a @position-try at-rule-lal saját fallback-pozíciókat definiálhatsz eltérő margókkal és méretezéssel, a position-visibility: anchors-visible pedig el is rejti a tooltipet, amikor az anchor kigörög a képből. Ez utóbbi a spec legfrissebb rétege: 2026 januárja óta számít mindenhol elérhetőnek.

A kedvenc részem: nulla konfiguráció

A Popover API-val kombinálva a dolog már-már gyanúsan kényelmes. A popovertarget attribútum ugyanis implicit anchor-kapcsolatot hoz létre a gomb és a popover között – se anchor-name, se position-anchor nem kell:

<button popovertarget="muveletek">Műveletek</button>
<div id="muveletek" popover>…</div>
[popover] {
  /* a popover UA-stílusa (inset: 0; margin: auto) középre tenné */
  inset: auto;
  margin: 0;
  position-area: bottom span-right;
  position-try-fallbacks: flip-block;
}

Az eredmény egy dropdown, ami a top layerben él (semmi nem takarja ki, az ősök overflow-ja nem érdekli), a gombhoz tapad, Esc-re és háttérkattintásra záródik, a viewport szélénél átfordul – nulla sor JavaScripttel. Ezen a ponton a megszokott „npm-csomag kell egy legördülő menühöz” felállás kezd elég nehezen védhető lenni.

Apróbetűs rész

  • Ha több elem kapja ugyanazt az anchor-name-et, a DOM-sorrendben utolsó nyer. Ismétlődő komponenseknél – lista, ahol minden sornak saját menüje van – ez elsőre meglepetés; az anchor-scope tulajdonsággal korlátozható a név a komponens részfájára.
  • Csak absolute vagy fixed pozicionálású elemre működik – a position-anchor önmagában nem csinál semmit.
  • A szintaxis menet közben változott: az inset-area-ból position-area lett, a position-try-options-ból position-try-fallbacks – a Chrome 129 óta már az új nevek élnek. A 2024-es tutorialok (és az azokon tanult AI-válaszok) egy része ezért ma is halott szintaxist mutat.
  • A kapcsolat tisztán vizuális: fókuszsorrendről, aria-expanded-ről továbbra is te gondoskodsz. Az anchor positioning a Floating UI-t váltja ki, nem az akadálymentesítést.
  • Elemhez tudsz horgonyozni, ponthoz nem: az egérpozíciónál nyíló context menühöz vagy a szövegkijelöléshez tapadó buborékhoz marad a JS (a Floating UI-ban ezek a virtual elementek).

Használnám-e productionben?

A támogatási sor: Chrome és Edge 125 (2024. május), Safari 26 (2025. szeptember), Firefox 147 (2026. január 13.). Vagyis friss Baseline – ami azt is jelenti, hogy egy tavalyi Safari vagy a jelenlegi, vállalati gépeken tipikus Firefox ESR 140 még semmit nem tud belőle.

Belső adminon, kontrollált böngészőflottán: igen, azonnal, gondolkodás nélkül. Publikus felületen árnyaltabb a kép. Tooltipnél az elegáns út a progresszív: @supports (anchor-name: --a) mögött anchor positioning, alatta egy egyszerű, fixen elhelyezett fallback – egy rossz helyen felugró tooltip bosszantó, nem tragédia. Ahol viszont a pozicionálás funkcionális – navigációs dropdown, date picker –, ott vagy marad még egy darabig a Floating UI, vagy ott az OddBird polyfillje; utóbbi korrekt munka, de a korlátait (nincs position-try-order, RTL-lel és vertikális írásmóddal nem boldogul) érdemes elolvasni, mielőtt ráépülsz.

A heti 80 millió letöltés nem fog eltűnni jövő keddre: a React-ökoszisztéma tele van Floating UI-ra épülő komponenskönyvtárral, és azok lassan mozdulnak. De új projektben ma már az a kérdés, hogy miért kellene JavaScript egy tooltipnek, nem az, hogy hogyan. Húsz év után a „hova kerüljön ez a doboz ahhoz képest” végre ott dől el, ahol mindig is kellett volna: a layoutmotorban.

Források

Leave a Reply

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük