tests/golden/scroll-trace.json · 2026-08-20
Schadet Smooth Scroll der Performance?
Die Frage wird fast immer an der falschen Größe gestellt. Frame-Zeit während eines Scrolls ist das, was alle profilen, und an den zwei Zielen dieses Repositorys ging sie in die andere Richtung: Der Lenis-Kandidat fuhr einen 1200-px-Wheel-Burst mit 8,54 ms mittlerer Frame-Zeit, die nicht überarbeitete Referenz denselben Burst mit 49,17 ms. Die Referenz zeichnet zwölf Canvas-Elemente und eine WebGL-Szene; der Kandidat zeichnete an dem Tag, an dem dieser Trace entstand, keines. Der Unterschied ist die Seite, nicht der Lerp.
Was einem Smooth Scroller wirklich anzulasten ist, ist Dauerlast — das, was die Seite vom Browser weiter verlangt, nachdem der Besucher aufgehört hat. Das lässt sich auf eine Zahl bringen, rAF-Callbacks pro Sekunde, während die Seite steht und nichts angefasst wird, und das Messgerät passt in einen Satz: Das Trace-Werkzeug legt um window.requestAnimationFrame einen Zähler, der an die native Implementierung weiterreicht, wartet dreitausend Millisekunden und teilt durch drei.
Das Artefakt, aus dem diese Zahlen stammen, wurde am 2026-08-20 committet und trägt einen leeren Aufnahme-Zeitstempel, deshalb ist das Commit-Datum das einzige Datum, das dieser Artikel ehrlich nennen kann. Das zählt doppelt, denn der gemessene Kandidat hatte kein GSAP, kein WebGL und keine Canvas-Elemente — was die halbe Erklärung dafür ist, warum seine Frame-Zeit so aussieht, wie sie aussieht.
- 8,54 ms mittlere Frame-Zeit, Kandidat während des Wheel-Bursts
- 49,17 ms mittlere Frame-Zeit, Referenz zwölf Canvas-Elemente und eine WebGL-Szene
- 134,3 rAF-Aufrufe pro Sekunde im Ruhezustand, Referenz mitten im Dokument geparkt, unberührt
- 0 rAF-Aufrufe pro Sekunde im Ruhezustand, Kandidat gleiche Sonde, gleicher Lauf, gebremster Smoother
Keine der beiden Frame-Zeiten ist eine Produktmessung, und das Repository sagt das unverblümt: Das Golden-Werkzeug läuft unter einem erzwungenen Software-Renderer, wo Routen ohne GL um 120 Hz liegen und GL-Routen um 16 Hz, und die Hz-Werte sind ein Artefakt dieses Werkzeugs und dürfen nie als Produkt-Performance zitiert werden. Was den Vorbehalt überlebt, ist der Vergleich — zwei Ziele, eine Maschine, eine Methode.
src/runtime/scroll/lenis-options.js:26-141
Was sagt die Konfiguration eigentlich voraus?
Die gesamte Scroll-Konfiguration dieser Website sind acht gesetzte Optionen in einem eingefrorenen Objekt, jede mit ihrer Quelle in einer eigenen Zeile, dazu eine Tabelle mit zehn Optionen, die absichtlich nicht übergeben werden — exportiert, damit ein Test das Negative prüfen kann, statt darauf zu vertrauen, dass ein Kommentar wahr bleibt. Die acht sind lerp 0.1, smoothWheel true, syncTouch false, touchMultiplier 1.25, autoResize true, autoRaf false, anchors false und infinite false.
Die Regel für diese Trennung steht oben in der Datei, und sie ist eine Aussage über Aussagen: Eine Option, die die alte Website als Bibliotheks-Default erbt, wird auch hier geerbt statt wiederholt, denn syncTouchLerp 0.075 hinzuschreiben würde einen Wert einfrieren, der der gepinnten Version gehört. Einen Default zu wiederholen macht aus „wir liefern das Verhalten von Lenis 1.1.20“ ein „wir liefern unsere Lesart des Verhaltens von Lenis 1.1.20“, und das ist eine andere und schlechtere Aussage. Drei Werte stehen trotzdem da — autoRaf, anchors und infinite — weil jeder von ihnen eine Entscheidung ist und keine Hinnahme.
lerp 0.1 macht aus dem Gefühl Arithmetik statt Meinung. Die Bibliothek dämpft mit eins minus dem Exponential von minus lerp mal sechzig mal dem Frame-Delta, die Kurve ist also eins minus e hoch minus sechs t, und die Meilensteine fallen daraus heraus: die halbe Strecke bei ln 2 durch sechs, also 116 ms, neunzig Prozent bei 384 ms, neunundneunzig Prozent bei 767 ms. Das sind Vorhersagen, die vor dem Trace standen.
| Meilenstein | lerp 0.1 sagt voraus | Kandidat | Referenz |
|---|---|---|---|
| 50 Prozent der Strecke | 116 ms | 141 ms | 275 ms |
| 90 Prozent | 384 ms | 408 ms | 559 ms |
| 99 Prozent | 767 ms | 766 ms | 950 ms |
| Mittlere Frame-Zeit im Burst | nicht vorhergesagt | 8,54 ms | 49,17 ms |
| Endposition nach 1200 px | nicht vorhergesagt | 1197 px | 1200 px |
Der Kandidat landet an der Hälfte 25 ms hinter der Theorie und bei neunundneunzig Prozent eine Millisekunde davor. Die Referenz ist an jedem Meilenstein langsamer, obwohl sie denselben Lerp fährt, und das ist das Klarste im ganzen Trace: Was ein Besucher spürt, ist das, was die Seite zeichnet, nicht die Glättungskurve. Vergleicht man beide in Frames statt in Millisekunden, meldet genau dieselbe Kurve einen vierfachen Unterschied, und zwar in die falsche Richtung — deshalb hält das Trace-Werkzeug Frame-Zahlen und Spitzenpixel pro Frame fest, ohne sie je über die Ziele hinweg zu vergleichen.
a15 §4.3 passivity census · 433 registrations
Wie viele Listener können ein Scrollen wirklich blockieren?
Mach deine Scroll-Listener passiv ist der Rat, den alle nachsprechen, und beim scroll-Event ist er Aberglaube. Das scroll-Event ist ein generisches Event und nicht abbrechbar — es gibt keine Standardaktion zu verhindern —, ein nicht-passiver scroll-Listener blockiert also exakt nichts. Passivität ändert das Browser-Verhalten nur bei Ereignissen, die das Scrollen aufgehalten hätten, wären sie abgebrochen worden: touchstart, touchmove, wheel und mousewheel.
Die hier geprüfte alte Website registriert auf ihrer Startseite elf Scroll-Listener, und vier davon werden als nicht-passiv gemeldet. Nicht einer der vier wurde mit passive false registriert. Es sind Registrierungen mit Default-Passivität — ein nacktes Boolean oder ein fehlendes drittes Argument — und selbst wenn sie ausdrücklich gewesen wären, hätte es nichts geändert. Die Registrierungen, die ein Scrollen wirklich aufhalten, liegen woanders auf dieser Seite, und es sind acht.
Der Fußabdruck des Smoothers selbst ist kleiner als die Folklore und in einer einzigen Konstante nachlesbar. Die gepinnte Quelle codiert ein einziges Listener-Optionsobjekt mit passive false fest ein und benutzt es für genau vier Registrierungen — wheel, touchstart, touchmove und touchend auf dem Wurzelelement — und die müssen nicht-passiv sein, denn preventDefault auf dem wheel-Event aufzurufen ist überhaupt erst die Art, wie die Glättung existiert. Ein fünfter Listener, scroll auf dem Wrapper, ist mit einem nackten false registriert, und in seinem Handler steht kein einziger preventDefault-Aufruf. Jedes Audit-Werkzeug zählt ihn mit. Nichts, was er tut, kann blockieren.
Die Sonde meldet für den Rebuild sechs scroll-, wheel- und touch-Listener, vier davon nicht-passiv, gegen neununddreißig und siebenundzwanzig auf der Referenz. Beide Nicht-passiv-Zahlen sind Obergrenzen und keine Zählungen, und das Repository sagt das in seinen eigenen Vorbehalten: Die Sonde liest Passivität nur daran ab, ob das dritte Argument ein Objekt ist, ein nacktes Boolean wird also auch dort als nicht-passiv gebucht, wo der Browser seine Regel passiv-per-Default anwendet. Siebenundzwanzig ist eine Obergrenze für die Referenz; acht ist die belastbare Zahl.
Und damit zum Nützlichsten auf dieser Seite, einem Fehler in der eigenen Prosa dieses Repositorys. Sechs Dateien hier behaupten, zwei der alten nicht-passiven Scroll-Handler kosteten rund 1470 Millisekunden. Tun sie nicht. 1469,8 ist die Millisekunde, in der der letzte der elf Listener registriert wurde, ausgelesen aus einer Tabelle, deren erste Spalte mit t in Millisekunden überschrieben ist — ein Registrierungs-Zeitstempel, keine Kosten, und nirgends im Bestand existiert eine Kostenmessung dieser Handler. Die technische Entscheidung, die an der Zahl hängt, stimmt weiterhin, denn diese Handler verhindern nichts und sollten passiv sein. Die Zahl, die an der Entscheidung hängt, misst nichts.
scroll-controller.js:53-70, :505-522
Warum schläft der Smoother, und was ging kaputt, als er es lernte?
Lenis 1.1.20 meldet sich im Ruhezustand nicht selbst ab. Sich selbst überlassen nimmt es sich alle sechzehn Millisekunden einen Frame, um eine Position zu interpolieren, die sich nicht mehr bewegt — genau die Dauerlast, um die es in der Messung oben geht. Zwei Teile beseitigen sie: Die Frame-Uhr plant keinen Frame ein, wenn nichts Lauffähiges abonniert ist, und der Scroll-Owner öffnet sein eigenes Abo bei Eingabe und schließt es zwölf stillstehende Frames, nachdem sich die Position beruhigt hat.
Beide Konstanten sind im Code selbst als gewählt und nicht als gemessen deklariert, und diese Ehrlichkeit ist der Punkt. 0.05 px liegt unter der kleinsten Bewegung, auf die irgendein Konsument dieser Website reagieren kann, und zwölf Frames sind bei 60 Hz etwa zweihundert Millisekunden — bequem hinter dem Auslauf einer lerp-0.1-Animation, die alle zweiundzwanzig Frames rund neunundachtzig Prozent ihrer Reststrecke verliert. Die Datei hält fest, dass beide zu gemessenen Zahlen werden, sobald der erste Scroll-Feel-Trace aufgezeichnet ist.
- Im Ruhezustand gar kein Abo Eine frisch gebootete Seite hält keinen Frame, und zwar mit Absicht: Es gibt nichts zu glätten, bis sich jemand bewegt, und beim Booten aufzuwachen würde ein Dutzend Frames damit verbrennen, zu beweisen, dass die Position immer noch null ist. Ein Test prüft, dass die Uhr nach init leer ist.
- Eingabe öffnet das Gate wheel, touchstart, keydown und resize, alle passiv registriert, dazu der scroll-Emitter der Bibliothek selbst — der für die Scrolls feuert, die sie nicht ausgelöst hat, sodass auch ein Zug am Scrollbalken, die Tastatur oder die Ende-Taste sie weckt.
- Die Uhr der Bibliothek neu setzen Setz ihre gespeicherte Zeit auf null zurück, bevor der erste Frame läuft. Lässt du das weg, bekommt der Smoother ein Delta in der Länge der Pause gereicht, und eine geglättete Geste landet als sofortiger Sprung.
- In zwei Phasen abonnieren Index null der Lese-Phase rückt den Smoother vor; die Compute-Phase veröffentlicht die Position und lässt das Gate laufen. Die alte Website erkauft sich diese Reihenfolge mit einem Boolean-Flag am Animations-Ticker. Hier ist sie strukturell, denn ein Flag kann man vergessen, eine Phase nicht.
- Jeden Frame messen Beschäftigt heißt: Die Bibliothek meldet sich selbst als scrollend, oder die Position hat sich um mehr als 0.05 px bewegt. Jeder beschäftigte Frame setzt den Idle-Zähler auf null zurück.
- Nach zwölf Idle-Frames schlafen Beide Abos schließen. Die Uhr plant dann von sich aus nichts mehr ein, weil ihr Zähler lebender Abonnenten null erreicht hat und ihr Scheduler früh zurückkehrt.
Schritt drei existiert, weil das Schließen des Abos das Gefühl kaputt gemacht hat, und der Bruch wurde vor der Reparatur gemessen. Die Bibliothek berechnet ihr Delta als aktuellen Zeitstempel minus dem, den sie im letzten Frame gespeichert hat, und während das Abo schläft, rückt diesen gespeicherten Zeitstempel nichts vor — der erste Frame nach dem Aufwachen reicht der Dämpfungsfunktion also die gesamte Stillstandszeit des Besuchers. Bei zwanzig Sekunden Pause ist das Exponential in einem einzigen Frame durch. Vor der einzeiligen Reparatur gemessen: Ein 1200-px-Wheel kam in vier Frames an, mit einer Spitze von 1200 px pro Frame, und das ist überhaupt keine Glättung mehr.
Die Reparatur setzt die gespeicherte Zeit der Bibliothek vor dem ersten Frame nach dem Aufwachen auf null zurück, und ein Unit-Test nagelt das fest: Der erste Frame rückt exakt null Pixel vor, der zweite rund 114 px der 1200. Der Kommentar über dieser Zeile ist die These dieses ganzen Artikels in einem Satz — eine Bibliothek, der ihre eigene Schleife gehört, hat dieses Problem nie, weil sie nie schläft, und nie zu schlafen ist genau der Preis, den dieses Gate vermeiden soll.
Ankernavigation ist die andere Hälfte davon, die Scroll-Position zu besitzen, und dort brechen die meisten Smooth-Scroll-Einbauten still die Tastatur — Smooth Scroll, Fokus und History.
src/vendor/lenis/lenis.mjs:585-601 · P11-04
Was gibt es zurück, wenn man die Touch-Glättung abschaltet?
syncTouch wurde auf dieser Website heute von true auf false gedreht, und der Grund ist ein Defekt und keine Vorliebe. Gemeldet war, dass die Seite auf iOS Safari oben und unten beschnitten aussieht und dass sich die Browser-Leiste anders verhält als auf jeder anderen Seite. Die naheliegende Reparatur — die Viewport-Einheiten ändern — hätte das Symptom behandelt und die Ursache laufen lassen.
Die Bibliotheks-Hälfte der Diagnose ist in der gepinnten Datei Zeile für Zeile nachprüfbar, die als Bytes mit einem SHA-256 committet ist, den der Build prüft. Mit syncTouch true macht ein Touch-Event den Glättungszweig wahr, der Handler ruft preventDefault, und die Bibliothek treibt die Position selbst. Jedes touchmove auf einem Telefon wird abgebrochen.
204 var listenerOptions = { passive: false };
211 el.addEventListener('wheel', onWheel, listenerOptions);
212 el.addEventListener('touchstart', onTouchStart, listenerOptions);
217 el.addEventListener('touchmove', onTouchMove, listenerOptions);
222 el.addEventListener('touchend', onTouchEnd, listenerOptions);
448 wrapper.addEventListener('scroll', onNativeScroll, false);
// drittes Argument ist das capture-Boolean, Passivität ist also Default —
// und in onNativeScroll (:631-660) steht überhaupt kein preventDefault
585 const isSmooth = options.syncTouch && isTouch
|| options.smoothWheel && isWheel;
586 if (!isSmooth) {
587 this.isScrolling = 'native';
588 this.animate.stop();
589 event.lenisStopPropagation = true;
590 return;
591 }
601 event.preventDefault(); // nur erreicht, wenn isSmooth true ist
Was ein abgebrochenes touchmove dann kostet, ist die Diagnose dieses Repositorys und sollte auch so gelesen werden. Safari zieht seine Adressleiste als Reaktion auf eine Scroll-Geste des Nutzers ein; ein programmatischer Scroll ist keine; die Leiste zieht sich also nie ein, und jede in vh bemessene Box wird auf den großen Viewport bemessen — eine Höhe, die die Seite erst hat, wenn die Leiste weg ist. Eine Aussage von Apple oder WebKit selbst zu dieser Regel ließ sich nicht finden. Der Mechanismus in der Bibliothek ist in der Quelle lesbar; das Browser-Verhalten, auf dem er beruht, ist berichtet, breit beobachtet und vom Hersteller nicht dokumentiert.
Mit syncTouch false nimmt derselbe Code den Zweig in Zeile 586, markiert den Scroll als nativ, stoppt die Animation und kehrt zurück, ohne irgendetwas zu verhindern. Die Plattform bekommt ihr Momentum, ihr Gummiband, ihre Adressleiste und ihre Bedienungshilfen zurück, und die Wheel-Glättung bleibt unangetastet, weil smoothWheel eine eigene Option und weiterhin true ist. Beachte, was sich nicht ändert: Diese vier Registrierungen sind immer noch nicht-passiv. Was sich ändert, ist, ob preventDefault überhaupt je aufgerufen wird.
Eine Sache musste danach von Hand nachgebaut werden. Auf dem Telefon ist die Projektleiste eine sticky, per Scroll gescrubbte Spur, die sich liest wie etwas zum Wischen und keines war, ein seitlicher Finger tat also gar nichts. Der Ersatz ist der einzige ausdrücklich nicht-passive Listener, den diese Website außerhalb des Smoothers hinzufügt: ein touchmove-Handler, der nur verhindert, solange eine waagerechte Achsensperre eingerastet ist, jenseits von acht Pixeln Spiel und innerhalb eines Kegels von etwa dreißig Grad — und er bewegt die Seite statt der Leiste, damit nie zwei Besitzer auf einer sichtbaren Position sitzen.
Die Leiste, die diese Geste antreibt, sitzt auf der Projektübersicht, wo dieselbe Messdisziplin auf zehn Builds angewandt wird.
src/vendor/gsap/ScrollTrigger.js:61-64
Wenn der Smoother keinen Frame hält, was läuft dann noch?
Die Null im Ruhezustand gilt für den Build, an dem sie gemessen wurde, und dieser Build ist fünfundneunzig Minuten älter als der Tag, an dem GSAP in diesen Baum kam. Auf der Seite, wie sie heute steht, ist die ehrliche Zahl nicht null. Sie ist eins, und diese Eins gehört nicht dem Smoother.
ScrollTrigger startet beim Aktivieren ein leeres, sich selbst neu einplanendes requestAnimationFrame und lässt es für die Lebensdauer der Seite laufen, mit dem Kommentar der Bibliothek daneben, es gebe das, weil Bildschirm-Repaints in manchen Browsern nicht gleichmäßig kamen, solange nichts in der Warteschlange stand. Es lässt sich nicht abschalten. Daneben läuft ein setInterval viermal pro Sekunde. Lenis zu entfernen würde keines von beiden entfernen.
- ScrollTrigger, die Repaint-Schleife
- Eine leere Funktion, die sich selbst ein frisches requestAnimationFrame zurückgibt, gestartet in ScrollTrigger.enable und nie gestoppt. Die Bibliothek liefert dafür keinen Schalter.
- ScrollTrigger, das Sync-Intervall
- Ein setInterval mit 250 ms — viermal pro Sekunde für die Lebensdauer der Seite, in ScrollTrigger.js:2110.
- Die Frame-Uhr
- Nichts. Ihr Scheduler kehrt früh zurück, wenn der Zähler lebender Abonnenten null ist, weshalb eine vollständig degradierte Seite ebenfalls anhält, statt höflich vor sich hin zu laufen.
- Der Scroll-Owner
- Nichts, zwölf stillstehende Frames nach der letzten Bewegung — und gar nichts auf einer Seite, die noch niemand angefasst hat, geprüft von einem Test, der die Uhr nach init auf leer prüft.
- Das Budget, das die Linie hält
- Höchstens ein fremder Callback pro Frame bei allen montierten Systemen aus Stufe 13, in der Quelle des Ruhe-Gates selbst als Pro-Frame-Wert von 1,15 oder weniger festgehalten.
Auch deshalb behauptet der Rebuild nicht, eine einzige Frame-Schleife zu haben. Er erklärt zwei Frame-Quellen mit einer festgelegten Beziehung: die Frame-Uhr, der jedes rAF aus eigener Hand gehört, und den GSAP-Ticker, dem der eigene Vorlauf der Animationsbibliothek gehört und auch bleibt. GSAP wird nicht auf die Uhr gezwungen, und das ist eine Entscheidung und kein Versäumnis — ein Tween anzulegen weckt den Ticker von innerhalb der Bibliothek, ihn schlafen zu legen hält also nicht, und die Funktion, die ihn von außen treiben würde, ist keine öffentliche API.
Was die zweite Quelle davon abhält, eine dauerhafte zweite Schleife zu werden, sind drei zugesicherte Eigenschaften statt eines Versprechens: das eigene autoSleep des Tickers, die Tatsache, dass ScrollTrigger nur auf Touch-Safari einen Ticker-Listener hinzufügt, und ein Ruhe-Budget, gemessen an der Stelle der Seite, an der die meisten Systeme gleichzeitig gebremst leben. Der Sinn, all das zu benennen, ist: Die Antwort auf schadet Smooth Scroll der Performance lautet auf dieser Seite, dass der Smoother nichts hält und etwas anderes schon.
ADR-008 · alternatives considered
Wären natives Scrollen oder ein Transform-Smoother billiger gewesen?
Die Eigenschaft, die das entschieden hat, ist einen Satz lang: Der geglättete Wert ist der Scroll-Offset des Dokuments. Weil die Bibliothek das echte Fenster bewegt, erben jeder getBoundingClientRect-Aufruf auf der Seite und jedes sticky-Pin die Glättung umsonst, und zehn unabhängige Leser sind sich einig, ohne dass man es ihnen sagt. Gemessen: window.scrollY war an allen siebenundzwanzig Kontrollpunkten über drei Routen gleich dem Scroll-Wert, den die Bibliothek meldet, und die Geschwindigkeit an jedem davon null.
Jede Alternative, die etwas anderes glättet, muss diese Einigkeit ausdrücklich wiederherstellen, in jedem Konsumenten, für immer. Das ist die Achse, nach der die Tabelle unten sortiert ist — Mechanik, nicht Geschmack.
| Ansatz | Was es bewegt | Gemessen oder belegt |
|---|---|---|
| CSS scroll-behavior smooth | Nur Fragment-Navigation und die Scroll-APIs des CSSOM; Scrolls des Nutzers bleiben unberührt, und die Kurve legt der Browser fest | Baseline seit März 2022 |
| CSS scroll-driven animations | Bindet eine Animation an eine Scroll-Position. Ein anderes Feature, kein Wheel-Lerp | 85,43 Prozent globale Unterstützung |
| GSAP ScrollSmoother | Ein Transform per matrix3d auf einem Wrapper-Inhalt, das einen neuen Containing Block schafft und jeden Rect-Aufruf der Seite umdeutet | 3 defensive Strings im alten Bundle, kein Plugin |
| Locomotive Scroll v5 | Hat den eigenen Virtual-Scroll-Container fallen gelassen, auf Lenis neu gebaut und die eigene sticky-Implementierung gelöscht | v5-Migrationsleitfaden |
| Lenis auf dem echten Offset | Den Scroll-Offset des Dokuments selbst, den jeder Konsument ohnehin liest | 27 von 27 Kontrollpunkten stimmen überein |
Die eigene achtzig Zeilen lange Dämpfungsschleife zu schreiben ist die verbleibende Option, und sie ist nur bis zur zweiten Woche billiger. Aufgegeben wird damit der Ausstieg über den composed path, der Abgleich mit den nativen Scrolls, die die Bibliothek weiterhin bekommt — ein Zug am Scrollbalken, die Tastatur, Pos1 und Ende —, das Gummiband an den Enden des Dokuments und ein Zustandsautomat, den der Rest der Seite mitliest.
Der Pfad für reduzierte Bewegung ist die Stelle, an der die Entscheidung bewiesen und nicht behauptet wird. Unter prefers-reduced-motion reduce baut diese Website überhaupt keinen Smoother — nicht gebaut und zerstört, nicht gestoppt — und der eine Scroll-Listener, der in diesem Modus installiert wird, ist ausdrücklich passiv, weil sein Handler nie preventDefault ruft und ihn anders zu registrieren reine Kosten im Haupt-Thread wären. Der Trace liest die Klassen des Wurzelelements bei sechs Breiten aus und findet die Smoother-Klasse bei keiner.
Dieses Gate existiert, weil der alte Pfad für reduzierte Bewegung ein gemessenes Desaster war. Er respektiert die Einstellung, indem er sechshundert Frames lang auf eine globale Variable pollt und die Instanz dann zerstört, denn der naheliegende Aufruf — den Smoother stoppen — hinterließ nachweislich eine Seite, die sich überhaupt nicht mehr scrollen ließ: Das Wheel bewegte sie null Pixel und die Ende-Taste bewegte sie null Pixel. Der Rebuild beweist das Gegenteil per Wheel und per Ende bei allen sechs Breiten, und an jeder erreicht die Ende-Taste exakt den eigenen Fuß des Dokuments.
Reduzierte Bewegung ist ein Modus und kein Schalter, und die Canvas-Hälfte dieses Arguments steht in was reduce für einen Renderer bedeutet.
tools/golden/scroll-trace.mjs, rest.mjs
Wie würdest du das auf deiner eigenen Seite messen?
Zwei Befehle erzeugen alles oben. Der erste fährt auf beiden Zielen einen 1200-px-Wheel-Burst und hält die Hüllkurve in Millisekunden fest, dazu die Regression für reduzierte Bewegung bei sechs Breiten, die Kosten im Ruhezustand, das Anker-Verhalten und die Passivität der Listener. Der zweite läuft elf Scroll-Halte ab, pollt die Frame-Uhr, bis ihr Zähler lebender Abonnenten null erreicht, und prüft das Ergebnis gegen eine geschlossene Liste der Bereiche, die einen Frame halten dürfen.
Das Pollen zählt mehr, als es klingt. Ein fester Wartewert misst, was gerade zufällig unterwegs ist: Das Logo-Diagramm im Fußbereich baut sich 2,8 Sekunden lang auf, ein Wartewert von 1,8 Sekunden erwischt es also mitten im Lauf und meldet einen Frame, der eine Animation ist und kein Leck. Bis zum Stillstand zu pollen trennt ein System, das nie loslässt, von einem, das noch fertig wird. Dasselbe Gate hat sich einmal selbst erwischt — eine Prüfung ging immer durch, weil die Seite langsam war, und fiel durch, als die Seite schneller wurde und sich innerhalb eines Fensters neu scharf stellte, das sie vorher verpasst hatte.
- Millisekunden, nie Frames
- Die zwei Ziele haben keine gemeinsame Bildrate, und die Dämpfung ist von Bauart her unabhängig von der Bildrate. Frame-Zahlen über beide hinweg zu vergleichen meldet eine identische Kurve als vierfachen Unterschied, und zwar in die falsche Richtung.
- Nicht-passiv ist eine Obergrenze
- Die Sonde liest Passivität nur daran ab, ob das dritte Argument ein Objekt ist, sie zählt also zu viel. Siebenundzwanzig auf der Referenz ist eine Decke und keine Zahl von Blockierern; belastbar sind die acht ausdrücklichen Registrierungen aus der Zählung im Audit.
- Kosten im Ruhezustand brauchen drei Läufe
- Drei identische Läufe derselben alten Seite ergaben 67, 61 und 61 rAF-Aufrufe in Fenstern von etwa einer Sekunde. Jeder Vergleich im Ruhezustand braucht die Streuung. Die 134,3 oben stammen aus einem anderen Messgerät unter einer anderen Bedingung und korrigieren diese nicht.
- Deklariert heißt nicht durchgesetzt
- Vier mögliche Budgets stehen in der Schwellenwert-Datei — Callbacks im Ruhezustand bei fünf pro Sekunde, verschiedene rAF-Aufrufstellen bei zwei, geleakte Globals bei eins, Konsolenfehler bei null — und ein grep nach ihrem Namen im Werkzeug-Verzeichnis liefert nichts. Sie sind deklariert und werden von keiner Prüfung gelesen.
- Was nie gemessen wurde
- Unterhalb von 992 px wurde auf keinem der beiden Ziele gemessen, und keiner der beiden Bestände enthält einen Wert für INP, CLS, Long Task oder LCP, weil die Sonde keinerlei PerformanceObserver installiert. Die 200-ms-Schwelle für INP in den Quellen unten ist der Standard und keine Messung dieser Seite.
Jedes Messgerät hat sein eigenes Grundrauschen, und dieses Rauschen zu messen ist ein Problem für sich — das Grundrauschen einer visuellen Regressionssuite.
Die technische Hälfte davon, wie diese Website gebaut ist, steht unter den vier Disziplinen.