Journal
Teknik3 min. læsning

Dropdown-menuen, der nægtede at blive klikket på

En menu blev tegnet perfekt, sad præcis hvor den skulle, og slugte hvert eneste klik. Synderen var en indgangsanimation, der aldrig rigtig sluttede. En kort rundtur i CSS stacking contexts, animation fill modes og hvordan man jager fejl, man ikke kan se.

BUBookatus udviklingsteam

Vi sendte en dropdown-menu ud, der ikke kunne klikkes på. Den åbnede fint. Den blev tegnet det rigtige sted, fuldt synlig, korrekt z-index, ingen fejl i konsollen. Klik gik lige igennem den og ned i panelet nedenunder, som om menuen var et spøgelse. Har du nogensinde kæmpet med den slags fejl, kender du allerede det forvirrende ved det: alting ser rigtigt ud, og intet virker.

At finde ud af, hvem der faktisk ligger øverst

Det første ærlige svar kom fra browseren og ikke fra at læse kode. document.elementFromPoint fortæller dig, hvilket element der i virkeligheden modtager et klik på en given koordinat. Vi pegede den mod midten af et menupunkt, og den returnerede et kort, der lå senere på siden. Menuen lå visuelt øverst og fysisk nedenunder.

js
// Who really gets the click at this point?const item = document.querySelector('[role="menuitem"]');const r = item.getBoundingClientRect();const top = document.elementFromPoint(r.x + r.width / 2, r.y + r.height / 2);console.log(top === item || item.contains(top)); // false -> something covers it // Walk the ancestors and list everything that creates a stacking contextfor (let el = item; el; el = el.parentElement) {  const cs = getComputedStyle(el);  const reasons = [];  if (cs.transform !== 'none') reasons.push('transform');  if (cs.filter !== 'none') reasons.push('filter');  if (cs.backdropFilter !== 'none') reasons.push('backdrop-filter');  if (cs.opacity !== '1') reasons.push('opacity');  if (cs.position !== 'static' && cs.zIndex !== 'auto') reasons.push('z-index');  if (reasons.length) console.log(el.className, reasons);}

Gennemgangen fandt den. Den header, der indeholdt menuen, meldte en transform, selvom intet stylesheet gav den en. Den eneste transform i nærheden var en indgangsanimation, en lille fade og et løft, der var færdig et halvt sekund efter, at siden var indlæst.

Fill modes lever længere end deres animationer

animation-fill-mode: both betyder, at elementet holder den første keyframe, før animationen starter, og beholder den sidste keyframe, efter den er slut. Beholder, for altid. Hvis en keyframe animerer transform, anvender den færdige animation stadig en transform, og et element med en transform bliver en stacking context. Hver efterkommers z-index er nu fanget inde i den og konkurrerer kun med lokale søskende, aldrig med resten af siden.

Vores side havde paneler efter headeren, som brugte backdrop-filter, og det skaber sin egen stacking context. To lukkede kasser, og rækkefølgen mellem dem afgjort af rækkefølgen i træet, ikke af den z-index vi blev ved med at hæve i forvirring. Menuen kunne have haft z-index en million. Inde i sin kasse kunne ingen udenfor høre den.

Her er fælden inde i fælden. Vi ændrede den sidste keyframe til transform: none i den tro, at den bevarede værdi så ville være harmløs. Den beregnede style meldte stadig en matrix. Browsere opløser en anvendt animations transform som en matrixværdi, og en identitetsmatrix tæller stadig som en transform, når det gælder stacking. Vores stacking context overlevede vores rettelse.

css
/* BAD: the finished animation keeps applying its last keyframe forever.   The header remains a stacking context for the life of the page. */.enter {  animation: rise 0.5s ease-out both;} /* GOOD: fill backwards only. The element returns to its natural state   after the animation, which is identical to the final keyframe anyway. */.enter {  animation: rise 0.5s ease-out backwards;} @keyframes rise {  from { opacity: 0; transform: translateY(10px); }  to   { opacity: 1; transform: none; }}

Reglen, vi skrev ned

Indgangsanimationer på containere bruger fill backwards, aldrig both. Backwards dækker det eneste hul, der betyder noget, nemlig billederne før en forsinket animation starter. Når animationen er slut, kobler elementet sig helt fri af den og holder op med at være en stacking context. Det visuelle resultat er det samme, fordi en velbygget indgang slutter i elementets naturlige tilstand.

En dropdown-menu, der åbner korrekt over frostede glaspaneler i et mørkt dashboard
Efter rettelsen: menuen tegnes over de frostede paneler og, vigtigere endnu, fanger sine egne klik.
  • Stol på elementFromPoint frem for dine øjne. Visuel rækkefølge og hit-testing kan være uenige, og kun den ene tager klikket.
  • Når z-index holder op med at give mening, så stop med at hæve den og begynd at jage stacking contexts. Gennemgangen af forældre ovenfor tager et minut.
  • Gå efter, hvad dine færdige animationer efterlader. getComputedStyle på en stille side fortæller dig, hvad der stadig er anvendt.
  • Udfyldte transforms er transforms, også når keyframen siger none.

Animationen varede et halvt sekund. Dens bivirkning havde slet ikke noget sluttidspunkt.

css stacking contextanimation fill modez-index virker ikkefrontend fejlfindingcss transform og stacking
Klar til at bruge det i praksis?

Bookatu giver dig en bookingside i dit eget look, depositum, medlemskaber, gavekort og påmindelser, med 0% kommission på dine bookinger.

Start gratis