Csendes scroll-kompenzáció: így működik a böngésző scroll anchoringja

Az overflow-anchor többnyire megakadályozza a zavaró ugrásokat, saját chat- és végtelen listánál viszont összeakadhat a kézi scrollTop-kezeléssel.

A chatablak tetejére megérkezik húsz régi üzenet, egy hirdetés betölti a helyét, vagy egy kép végre megkapja a valódi magasságát. Ha az olvasó épp egy régebbi bejegyzést néz, nem a dokumentum abszolút scrollpozícióját akarja megtartani, hanem azt a sort, ahol tartott. Ezt próbálja alapértelmezetten megoldani a böngésző scroll anchoring mechanizmusa.

A funkció legtöbbször láthatatlanul hasznos. De amikor egy saját végtelen listához kézzel igazítjuk a scrollTop-ot, a böngésző és az alkalmazáskód ugyanazért a pixelekért versenyez. Ilyenkor jelennek meg a „néha kétszer ugrik” jellegű hibák.

Üzenetlista, amelynél a felül beszúrt elemek után a görgetési pozíció kompenzálva marad

Nem egy rögzített ponthoz, hanem egy elemhez köt

A CSS Scroll Anchoring specifikáció szerint a böngésző egy látható DOM-elemet választ horgonynak. Ha egy felette lévő változás eltolja ezt az elemet, a scrollert ellenirányban elmozdítja. Az eredmény: a horgony nagyjából ugyanott marad a viewportban. A választás nem API-szerződés: mély, látható elemek előnyt élveznek, a fókuszban lévő elem és a keresési találat pedig kiemelt jelölt lehet.

Ezért nincs értelme arra építeni, hogy „a harmadik kártya biztosan horgony”. A mechanizmust layoutváltozások csillapítására kapjuk, nem pozicionáló API-ként. A kompenzáció ráadásul bizonyos változásoknál elnyomható; a specifikáció ilyenek között említi többek közt a transform, a méretek, a margó vagy a pozicionálás módosítását az érintett útvonalon.

A két jó stratégia

Hírfolyamnál, cikkoldalnál vagy képeket utólag betöltő felületnél általában hagyjuk dolgozni a böngészőt. Emellett továbbra is érdemes helyet foglalni a később érkező tartalomnak: a horgonyzás a már elgörgetett részt védi, nem helyettesíti a stabil layoutot és a képek méretmegadását.

Fordított chat- vagy naplólistánál viszont gyakran saját szabály kell: ha a felhasználó az alján van, maradjon az alján; ha régi üzeneteket olvas, az első látható üzenet maradjon a helyén. Ebben az esetben egyetlen gazdája legyen a scrollpozíciónak. Kapcsoljuk ki az automatikát azon a scrolleren a overflow-anchor: none szabállyal, majd a beszúrás előtti és utáni tartalommagasság különbségével korrigáljunk.

function nextScrollTop({ scrollTop, oldScrollHeight, newScrollHeight, clientHeight }) {
  const atBottom = oldScrollHeight - scrollTop - clientHeight <= 1;
  const addedHeight = newScrollHeight - oldScrollHeight;

  return atBottom
    ? newScrollHeight - clientHeight
    : scrollTop + addedHeight;
}

A tényleges beszúrás előtt olvassuk ki a scrollHeight, szúrjuk be a régi elemeket a lista elejére, majd az új scrollHeight és a függvény alapján állítsuk a scrollTop-ot. A scrollHeight a túlcsorduló tartalmat is méri, ezért pont azt a magasságtöbbletet adja, amelyet a felhasználó előtt kompenzálni kell. Az egy pixeles tolerancia a kerekítési eltérések miatt praktikus.

Virtualizált listánál ez csak akkor működik, ha a mérés és a kompenzáció ugyanabban a renderciklusban látja a ténylegesen megváltozott magasságot. Ha a sorok magasságát később egy kép, betűkészlet vagy aszinkron mérés növeli meg, azt külön kezelnünk kell, vagy hagynunk kell a natív horgonyzást. A két stratégia közötti választás tehát nem teljesítménytrükk, hanem tulajdonjog: a böngésző vagy az alkalmazás kezelje a stabil olvasási pozíciót.

Érdemes a saját kompenzációt a lista belső scrollerére korlátozni. Ne tegyünk automatikus scrollIntoView()-hívást minden új üzenethez: azzal az olvasó régi üzenetek között elfoglalt helyét írjuk felül. Az alsó pozíciót csak akkor kövessük, ha a beszúrás előtt valóban ott volt.

Ne keverjük ezt a scrollIntoView()-val. Az utóbbi aktív navigációs utasítás: egy konkrét elemet a látható tartományba visz. Scroll anchoringnál épp az a cél, hogy DOM-változás közben ne kelljen új helyre navigálnunk.

Az egyirányú kikapcsolás csapdája

Az overflow-anchor: none nem öröklődik, mégis kizárja az adott elem teljes részfáját a horgonyjelöltek közül. Ezt egy gyermek overflow-anchor: auto szabállyal nem lehet egyszerűen visszakapcsolni; kivétel a beágyazott, önálló scroller. Emiatt ne a body-ra tegyük hibakeresésből: egy közeli chatlistára vagy virtualizált panelre szűkítsük.

A jó gyors ellenőrzés: lassítsuk le a hálózatot, görgessünk a lista közepére, majd kérjünk régebbi oldalnyi adatot. Ha a tartalom mégis ugrik, előbb azt derítsük ki, ki írja a scrollTop-ot. Az esetek nagy részében nem a képlet rossz, hanem két, egymástól független kompenzáció fut egyszerre.

Források

Leave a Reply

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