Zum Artikel springen

Bewegung Erschienen Lesezeit 10 Min.

Was reduzierte Bewegung mit einem WebGL- oder Canvas-Hero machen sollte

Die CSS-Pauschalregel, die alle ausliefern, entscheidet, wie lange eine Animation läuft. Sie kann nicht entscheiden, auf welchem Frame sie stehen bleibt, welches Layout sie hinterlässt oder was ein Canvas noch zeigt, wenn seine Schleife weg ist — und bei einem Hero, den Three.js, Rive oder ein 2-D-Kontext zeichnet, sind genau diese drei die ganze Frage.

Kurz gesagt

Unter prefers-reduced-motion: reduce sollte ein WebGL- oder Canvas-Hero einen ganzen, ruhenden Frame halten und keine Frame-Registrierung: Nichts bewegt sich, und nichts ist leer. Die verbreitete CSS-Pauschalregel kann diesen Zustand nicht herstellen, denn animation-duration: 0.01ms entscheidet nur, wie lange eine Animation läuft — nicht, auf welchem Keyframe sie stehen bleibt, nicht, was ein gepinntes Layout tut, und überhaupt nichts über Pixel, die eine JavaScript-Laufzeit zeichnet. Auf dieser Website deklariert jedes animierte Subsystem eine von vier Antworten — off, end-state, shortened oder runs mit schriftlicher Begründung — und 61 committete Gate-Prüfungen in 17 Prüflisten belegen beide Hälften auf einmal: nirgends auf der Seite eine gehaltene Frame-Registrierung, und jedes der sieben Canvas-Elemente trägt weiterhin ein ruhendes Bild.

Was du mitnehmen solltest

  1. Die Pauschalregel beantwortet eine der vier Fragen, die die Einstellung stellt: animation-duration: 0.01ms entscheidet, wie lange — nie, auf welchem Frame du landest, in welchem Layout du zurückbleibst oder was ein Canvas noch zeigt, wenn seine Schleife stoppt.
  2. Die zwei Fehlerfälle sind symmetrisch, also prüfe beide Hälften in derselben Prüfung — nirgends eine gehaltene Frame-Registrierung, und keine Fläche ohne Bild darauf.
  3. Bei einer Keyframe-Animation ist die Dauer der falsche Hebel: Einen 18-Sekunden-Wireframe-Zyklus auf 0.01ms zu stauchen, lässt jeden Strich auf dem gelöschten Keyframe landen, und nur animation-name: none setzt sie außer Kraft.
  4. Ein ruhender Zustand ist etwas, das du baust, nicht etwas, das du bekommst — der Hero-Frame dieser Website war unbemerkt halb gerendert, bis zwei Aufnahmen bei fixierten Zeiten von 1,5 s und 9 s byteweise identisch zurückkamen.
  5. Ein Pin ist Layout und keine Bewegung, also muss reduce ihn zurücknehmen: Nur das gescrubbte Transform der Journey-Leiste abzuschalten, hat drei ihrer vier Kapitel seitlich aus dem Dokument geschnitten.
  6. Reduzierte Bewegung ist der einzig ehrliche Zustand, in dem sich eine animierte Website prüfen lässt: Derselbe axe-core-Durchlauf meldete ohne die Einstellung 707 schwerwiegende Kontrast-Knoten und mit ihr 5.

golden:webgl §7 · 4 checks

Was sollte reduzierte Bewegung mit einem Canvas-Hero machen?

Zwei Antworten werden häufig ausgeliefert, und beide sind falsch. Die erste lässt den Hero weiterlaufen, denn eine Media Query im Stylesheet erreicht nichts, was eine JavaScript-Laufzeit in einen GPU-Kontext zeichnet — die Einstellung ist gesetzt, die Seite sieht identisch aus, und wer um weniger Bewegung gebeten hat, bekommt exakt genauso viel davon. Die zweite entfernt den Hero und tauscht ein bewegtes Bild gegen ein Loch, samt dem gesamten ersten Eindruck der Seite.

Die Antwort hier ist ein ganzer Frame, und danach nichts. Unter reduce sind das Porträt, das Halbton-Raster und die angedockte Pose gezeichnet; die Pointer-Parallaxe und die Fluid-Spur nicht; und keine Frame-Registrierung bleibt bestehen. Entschieden wird das in einem einzigen Prädikat in der Laufzeit des Heros, wo die Einstellung einer von vier unabhängigen Gründen zum Anhalten ist, und der else-Zweig ist das, was aus dem Ergebnis einen ruhenden Zustand statt eines leeren macht: die Frame-Phasen abmelden, den Pointer schließen, dann noch einmal rendern, falls der Hero noch in seinem Gate liegt.

Der Rest dieses Artikels ist diese Entscheidung, verallgemeinert über neun Subsysteme, die drei Fehler, die es dafür zu beheben galt, und die Zahlen hinter den vier Werten unten.

  • 61 Gate-Prüfungen, die die Einstellung nennen von 817 in 17 Prüflisten
  • 7 Canvas-Elemente mit weiterhin sichtbarem Bild 1 WebGL-Hero, 1 Punktfeld, 5 Rive-Pfeile
  • 0 gehaltene Frame-Registrierungen an jeder der 12 Scroll-Positionen
  • 16 reduce-Blöcke in den Stylesheets verteilt auf 17 von 42 Dateien
Gezählt über die committeten Prüflisten in tests/golden/expectations und einen grep über src. Die 61 und die 817 sind Prüfnamen im versionierten Baum, nicht das Ergebnis eines Laufs.

src/styles/base/reset.css:38-49

Der Notausschalter ist richtig, und er erreicht weniger, als du denkst

Der Ausschnitt unten wird hier unverändert ausgeliefert, im ersten Cascade Layer, und nichts in diesem Artikel ist ein Argument dagegen. Er ist die Untergrenze. Wissenswert ist, warum er im ersten Layer steht und was er nachweislich nicht erreichen kann.

Bei important-Deklarationen dreht sich die Rangfolge der Cascade Layer um: Eine normale Deklaration gewinnt im späteren Layer, eine important-Deklaration im früheren. Die Layer-Reihenfolge lautet reset, tokens, base, layout, components, sections, utilities, overrides — das hier ist also die stärkste important-Regel im Stylesheet, und ein Unit-Test prüft beide Hälften davon: Layer null ist reset und enthält die Duration-Zeile, während der letzte Layer überhaupt keine important-Deklaration trägt.

Die Pauschalregel, und der Grund, warum sie dort steht, wo sie steht. Beide Blöcke sind aus dem Repository übernommen; der lange Kommentar unten steht dort im englischen Original.
/* src/styles/base/reset.css — der ERSTE Cascade Layer. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    /* dokumentierte Ausnahme: Nutzereinstellung schlägt Design */
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
 
/* For important declarations the layer order REVERSES: the declaration
   in the earliest layer wins (CSS Cascade 5, section 6.1). Nothing in a
   later layer can out-rank this at any specificity — and the correct
   escape hatch is therefore a NORMAL declaration in the last layer.
   Reaching for important there makes the rule weaker, not stronger. */

Die zwei Zeilen, in denen es nicht um Dauer geht, sind tragend. 0.01ms statt 0s erhält die Events transitionend und animationend, sodass eine Zustandsmaschine, die auf das Ende einer Transition wartet, weiterläuft; animation-iteration-count: 1 verhindert, dass eine als infinite deklarierte Animation bei 0.01ms zu einem sehr schnellen Flackern wird — dem Gegenteil dessen, worum gebeten wurde. Chris Coyier, der den Ausschnitt bekannt gemacht hat, hält im selben Beitrag fest, dass er nur dort greift, wo Bewegung vollständig aus CSS kommt, und dass er bei JavaScript-getriebenen Effekten Animationen schnell und schwindelerregend machen kann.

Den Schalter nach vorn zu stellen, hat einen ehrlichen Preis, und dieses Repository hält ihn fest, statt ihn zu umgehen. Für das Lade-Overlay war shortened bei 0,3 s spezifiziert — und das ist aus keinem späteren Layer erreichbar, denn eine normale Deklaration verliert gegen die important-Deklaration, und eine important-Deklaration in einem späteren Layer ist noch schwächer. Unter reduce steht die Marke also fertig da, statt gezeichnet zu werden, und der Burst ist augenblicklich. Das ist eine bewusste Verschlechterung gegenüber der Referenz, die so bleibt, weil die Nutzereinstellung über dem Design zu stehen das richtige Ergebnis ist.

Hier gehört eine Schlussfolgerung hin, keine Messung. Drei Regeln in späteren Layern dieses Repositorys setzen unter reduce transition-duration: 0.01s, und nach der Kaskadenregel aus dem Absatz oben sind das normale Deklarationen, die von der important-Deklaration im reset-Layer dominiert werden. Kein Gate im Harness liest unter reduce eine berechnete Transition-Dauer aus, das folgt also aus der Spezifikation und nicht aus einem Browser — genau die Sorte Regel, die sich in einer Codebasis ansammelt, ohne dass jemand merkt, dass sie wirkungslos ist.

home-hero.css:329-353

Die Dauer ist der falsche Hebel — die Frage ist, auf welchem Frame du landest

Der Hero trägt ein Wireframe, das sich in einem 18-Sekunden-Zyklus selbst zeichnet und wieder löscht. Stauche diesen Zyklus auf 0.01ms, und jeder Strich landet auf dem Keyframe, der gerade da ist — bei dieser Animation ist das der gelöschte: Die Pauschalregel hinterlässt, korrekt angewandt und genau das tuend, was sie sagt, eine leere Fläche. An der Dauer lässt sich auch nichts verhandeln, denn diese important-Deklaration ist die stärkste im Stylesheet.

Eine einzige Eigenschaft setzt sie außer Kraft. animation-name: none nimmt die Animation vollständig aus dem Spiel, und keine Dauer kann eine Animation wiederbeleben, die keinen Namen hat.

Der Ausweg, und die Pose, in der es zur Ruhe kommt. Der gelöschte Keyframe ist das, was die Pauschalregel von sich aus gewählt hätte; der Kopfkommentar steht dort im englischen Original.
/* src/styles/sections/home-hero.css — the hero wireframe.
   The reset layer would collapse the 18 s cycle to 0.01 ms and land
   every stroke on the ERASED keyframe, leaving an empty board. */
@media (prefers-reduced-motion: reduce) {
  .wfx-e {
    animation-name: none;
    stroke-dashoffset: 0;
  }
 
  .wfx-g2,
  .wfx-g3,
  .wfx-g4 {
    display: none;
  }
}
 
/* Eine Gruppe bleibt voll gezeichnet — genau dafür ist das Artboard da. */

Dieselbe Falle erwischt den Hinweis zur Geräteausrichtung, wo drei Elemente ein Handy animieren, das sich aufrichtet, während dahinter ein Bogen gezeichnet wird. Ihr erster Frame ist das liegende Gerät ohne Bogen — die eine Pose, die keine Information trägt — also parkt der reduce-Block alle drei stattdessen im Endzustand: das Handy aufrecht, die Marke aufrecht dazu, der Bogen vollständig gezeichnet und sein Kopf beleuchtet. Das ist end-state im Vokabular der vier Antworten, und die Begründung ist der allgemeine Fall: Die Information ist ein Ziel und keine Reise, also verliert eine Leserin, die um weniger Bewegung gebeten hat, die Geste und behält die Bedeutung.

Der dritte Fall ist kleiner und leicht zu übersehen. Der Cursor im Code-Panel blinkt über eine keyframes-Regel, die der Tipp-Controller nie für sich beansprucht — die Tipp-Kette startet also gar nicht, und das Blinken wäre das Einzige geblieben, was sich auf der Seite noch bewegt. Es wird im eigenen reduce-Block des Abschnitts namentlich stillgelegt, aus demselben Grund wie das Wireframe.

motion-architecture.md:451-460

Vier Antworten, und warum runs eine schriftliche Begründung braucht

Die Einstellung ist kein Boolean, und die Spezifikation beschreibt sie auch nicht so. Media Queries Level 5 definiert sie als Erkennen des Wunsches nach weniger Bewegung auf der Seite, und MDN beschreibt die Pflicht der Autorin als ein Interface, das bewegungsbasierte Animationen entfernt, reduziert oder ersetzt — drei erlaubte Reaktionen statt einer. Das ist die externe Grundlage für eine Taxonomie, und diese Website deklariert für jeden animierten Effekt eine von vier Antworten.

Das Vokabular, mit dem jeder Effekt dieser Website registriert wird, und was jede Antwort im ausgelieferten Code bedeutet — nicht im Plan.
Antwort Was sie bedeutet Wo sie hier vorkommt
off Startet nie. Das Element rendert in seinem fertigen Endzustand Der Hamburger-Morph, das Touch-Ziehen der Leiste, die Pointer-Platte
end-state Die Animation entfällt, ihr letzter Frame wird sofort angewandt Der WebGL-Hero, die fünf Rive-Pfeile, das Punktfeld, jedes Einblenden
shortened Läuft, mit verkürzter Dauer Für den Lade-Burst benannt, als aus späterem Layer unerreichbar vermerkt
runs Läuft unverändert und braucht eine schriftliche Begründung Genau eine Szene auf der ganzen Website: das Kapitel der Hintergrundfarbe

Die Deklaration ist keine Review-Konvention. Ein Effekt lässt sich ohne reset- und dispose-Funktion gar nicht bei der Motion-Runtime registrieren, und die Registrierung wirft einen TypeError, wenn eine davon fehlt — ein Effekt, dessen ehrliche Antwort nichts ist, schreibt eine leere Funktion mit einem Kommentar, der sagt warum. Schweigen ist ein Absturz und keine Vorgabe, und genau das macht aus der Architekturregel etwas, das ein Build halten kann.

Das eine runs auf der Website ist eine Farb-Überblendung, die an der Scroll-Position hängt, und sie verdient die Ausnahme mit einer Messung statt einer Meinung: Nichts verschiebt, dreht oder skaliert sich, es gibt also keine Bewegung zu reduzieren. Sie abzuschalten, zerbrach messbar die Seite bei 390 px, wo das reduzierte Stylesheet den Wrapper des Rings auf einen Viewport zusammenzieht, die Fortschrittsquelle des Kapitels auf null geht und der Hintergrund über das ganze Kapitel hinweg rgb(237, 245, 243) hält. Das committete Gate prüft die Gegenbedingung, mit gepinntem Ring und Weg auf null: Der berechnete Hintergrund des body muss bis zum Deck immer noch unter einen Rotkanal von 120 abdunkeln.

7 canvases · 3 runtimes · 15 consumers

Subsystem für Subsystem: was reduce tatsächlich tut

Ein einziges Auslesen der Einstellung erreicht fünfzehn Laufzeitmodule, und jedes davon bringt eine andere Fläche zur Ruhe. Die Tabelle ist die Referenz: die Antwort, woraus der ruhende Zustand besteht, und die committete Prüfung, die fehlschlagen würde, wenn er es nicht wäre.

Aus dem ausgelieferten Quelltext gelesen, nicht aus den Spezifikationsdokumenten — zwei davon beschreiben für Rive noch eine ältere, leerere Antwort, die fünf leere Canvas-Elemente hinterlassen hätte.
Subsystem Antwort Woraus der ruhende Zustand besteht Committete Prüfungen
WebGL-Hero, Three.js end-state Ein ganzer Frame bei Delta null; mehr als die halbe Fläche beleuchtet; kein Pointer registriert 5
Rive-Pfeile, Canvas2D end-state Zehn Schritte von je einer Sechzigstelsekunde, einmal angewandt und gezeichnet 3
Punktfeld, Canvas2D end-state Die ruhende Streuung mit jedem abgetasteten Punkt, der Cursor-Ring außerhalb des Feldes geparkt 5
GSAP-Reveals off Gar nicht erst installiert: Text unbeschnitten, keine Wischer, alles als eingeblendet markiert 3
ScrollTrigger-Scrubs off Die gestaltete Pose des Abschnitts — der Ring eingefroren bei den minus 20 Grad aus dem Stylesheet 6
Lenis-Glättung off Nie konstruiert: natives Scrollen, sofort, und jede Methode funktioniert weiterhin 1
Zertifikats-Deck off Sechs vollständige Karten in einer einfachen Spalte, das Zertifikat zuletzt, kein Pause-Button 7

Zwei Zeilen lohnen eine Ausführung, denn sie zeigen, was ein ruhender Zustand im Bau kostet. Rives Antwort ist als Wert im Contract deklariert, statt in einem Zweig entschieden zu werden — der ausgelieferte Pfeil-Contract trägt settle — und zur Ruhe bringen heißt, die Zustandsmaschine zehnmal um je eine Sechzigstelsekunde vorzurücken und einmal zu zeichnen. Die Grenze ist fest und keine Schleife, die wartet, bis die Maschine sich nicht mehr ändert, denn eine schleifende Zustandsmaschine erreicht diese Bedingung nie. Zehn Schritte reichen, damit ein Übergang zwischen zwei Ruhezuständen ankommt.

Das Punktfeld hat einen eigenen Standbild-Modus, und der ist ausdrücklich nicht Frame null. Im normalen Lauf tastet ein Drittel der Punkte seinen Driftvektor in jedem Frame neu ab, der Rest verwendet den letzten weiter — das steckt im ausgelieferten Look und ist nicht nachträglich angeschraubt, Frame null ist also ein Drittel Bild. Der Standbild-Modus tastet jeden Punkt ab und parkt den Cursor-Ring weit außerhalb des Feldes, sodass der Frame unter reduzierter Bewegung die ruhende Streuung ist und kein eingefrorener Cursor-Klecks. Das Gate prüft die Füllung statt der Schleife: dieselbe Zahl gezeichneter Pixel und derselbe Alpha-Mittelwert im Abstand von 900 ms, und das Feld weiterhin im aktiven Satz, denn ein Umschalten der Einstellung, das das Feld aushängt, nimmt die Deckungsmessung des Headers mit.

pinTime(1.5) against pinTime(9)

Der Halbframe-Fehler, und die Frage, die ihn gefunden hat

Der ruhende Frame des Heros war monatelang falsch und sah die ganze Zeit richtig aus. Gefunden wurde er nicht durch Hinsehen, sondern durch die Forderung, dass der Frame deterministisch sein muss — das ist der übertragbare Teil dieser Geschichte: Auf einem Canvas sind die Korrektheitsfragen, die du mit dem Auge beantworten kannst, genau die schon beantworteten.

  1. Die Implementierung, die korrekt aussieht Unter reduce die Frame-Phasen abmelden und dann die Write-Phase einmal aufrufen. Es zeichnet, das Canvas ist nicht leer, und ein Screenshot davon überzeugt.
  2. Der schlummernde Fehler Write setzt mit dem zusammen, was die letzte Compute-Phase hinterlassen hat. Wo nie ein Compute gelaufen ist, sind das ein Zeit-Uniform von 0 und eine Hintergrundrausch-Textur auf null — dem ruhenden Frame fehlte also das eigene Feld des Halbton-Rasters, und das ist der größte Teil dessen, wie der Hero aussieht.
  3. Die Frage, die ihn ans Licht brachte Der Harness verlangte Determinismus statt Schönheit. Zwei Aufnahmen bei fixierten Zeiten von 1,5 s und 9 s kamen byteweise identisch zurück, und eine fixierte Zeit, die nichts ändert, ist ein Uniform, das keinen Shader erreicht.
  4. Die Behebung Compute laufen lassen und dann Write, bei einem Delta von null. Jeder Dämpfer behält seinen Wert, denn Dämpfung über keine Zeit gibt den Ausgangswert zurück, und die verstrichene Zeit rückt nicht vor. Es ist derselbe Frame, zu Ende gezeichnet, und nicht der nächste.
  5. Das Gate, das die Behauptung beweisbar macht 3 Sekunden fixieren, einmal rendern, dann prüfen, dass das Zeit-Uniform der Szene 3 liest. Erst zu fixieren ist Pflicht: Unter reduce ist eine nicht fixierte Zeit berechtigterweise 0, dort abgelesen beweist sie also in keine Richtung etwas. Ein halber Frame lässt sie bei 0 stehen, mit der Rausch-Textur auf null.
  6. Zurücklesen, niemals screenshotten Der Drawing Buffer wird nicht erhalten, der Canvas-Inhalt ist also undefiniert, sobald der Compositor ihn genommen hat, und ein Element-Screenshot ist ein Münzwurf. Im selben Durchgang rendern und Pixel lesen ist die einzige ehrliche Aufnahme.
Wie ein ruhender Frame entsteht, und wie die Fassung aufflog, die nur so aussah, als funktioniere sie.

Die Prüfliste um diese Behebung sichert vier Dinge zu, und keines davon ist ein Bild: mehr als die halbe Canvas-Fläche ist beleuchtet, der Frame ist ein ganzer Frame, kein Frame wird gehalten und kein Pointer ist registriert, und die Frame-Zahl ist 1.200 ms später unverändert. Das Letzte wiegt schwerer, als es klingt — ein Renderer, der still vor sich hin weitertickt, erfüllt jede statische Prüfung, die du über das Bild schreiben kannst.

Pixel zurückzulesen, statt Screenshots zu vergleichen, ist dieselbe Disziplin, die ein Canvas für Regressionstests braucht — dazu gibt es hier eigene Zahlen: das Rauschniveau eines Canvas-Screenshots.

home-journey.css:569-605

Die Hälfte, die keine Dauer-Regel erreicht: Layout und Zustand

Drei Abschnitte dieses Repositorys kamen unabhängig voneinander und mit eigenen Worten zum selben Befund: Der Schalter kann Layout nicht zurücknehmen, und er kann Zustand nicht zurücknehmen. Eine sticky-Platte pinnt weiterhin. Ein Transform, das ein Controller geschrieben hat, hält weiterhin. Eine Custom Property, die auf irgendeinen Zwischenwert gescrubbt wurde, behält, was zuletzt hineingeschrieben wurde. Nichts davon ist noch eine Animation, wenn die Einstellung gelesen wird, und nichts davon hat eine Dauer, die sich stauchen ließe.

Bei der Journey-Leiste hat das eine messbare Menge Inhalt gekostet. Sie ist ein horizontales System — vier Karten auf einer 4.052 px breiten Bahn in einem 1.440 px breiten Viewport — und das Einzige, was die anderen drei ins Bild holt, ist ein gescrubbtes Transform. Dieses Transform für reduce abzuschalten, ist richtig, denn ein scrollgetriebenes seitliches Gleiten ist genau die Bewegung, um die es bei der Einstellung geht; nur das abzuschalten, hat drei Viertel des Abschnitts hinter dem horizontalen Overflow des body abgeschnitten. Vor der Layout-Regel gemessen, kam das Dokument auf 14.884 px statt 18.709, und die fehlenden 3.825 waren die Kapitel, die niemand erreichen konnte.

ohne Einstellung 18.709 px
unter reduce, wie ausgeliefert 15.506 px
unter reduce, vor der Layout-Regel 14.884 px
Dokumenthöhe der Startseite bei 1440 mal 900, aus der Round-Trip-Prüfung in tools/golden/rest.mjs und der Notiz in home-journey.css.Die Balken sind maßstäblich zum animierten Dokument.

Ein kürzeres Dokument unter reduce ist richtig, und das Gate sagt das ausdrücklich: Die reduzierte Seite an ihrer animierten Länge zu messen, hieße zu behaupten, dass reduzierte Bewegung nichts ändert — das Gegenteil des Punkts. Gelten muss, dass kein Inhalt verloren geht, die Prüfung sammelt also jedes Journey-Panel ein und verlangt von jedem, dass es Fläche hat, dargestellt und sichtbar ist und innerhalb des horizontalen Bands des Dokuments liegt. Die Behebung gibt reduce dieselbe Darstellung wie einem Handy: Aus der Bahn wird eine Spalte.

Die Zustandshälfte davon erzeugt einen schlimmeren Fehler als eine fehlende Karte. Auf der Projektleiste löst der reduce-Block die Bühne aus dem sticky-Zustand, stellt horizontales Overflow und Scroll-Snapping wieder her, setzt das Transform der Bahn auf none und touch-action zurück auf auto — alles, damit ein veraltetes Attribut, das eine mitten in der Sitzung geänderte Einstellung hinterlässt, bevor die Runtime abgebaut hat, keine Leserin in einem acht Bildschirme langen Pin festhalten kann, aus dem sie nicht herausscrollt. touch-action wiederherzustellen ist keine Dekoration: Das Drag-Skript wird unter reduce nie installiert, ein geerbtes pan-y würde also genau die Geste verbieten, für die es den Fallback überhaupt gibt.

Die Website, die diese hier ersetzt hat, hatte die feinste Variante dieser Klasse. Ihr Seitenübergangs-Burst verkürzte sich unter reduce von 0,68 s auf 0,3 s, während die damit gepaarte Sichtbarkeits-Transition bei 0,68 s blieb — für rund 380 ms nach dem Verschwinden des Overlays landeten die Klicks eines Besuchers mit reduzierter Bewegung also auf einer unsichtbaren Ebene über dem ganzen Viewport. Eine nur teilweise angewandte Einstellung entkoppelt zwei Zeiten, die nur zusammen jemals richtig waren, und das sieht man weder auf einem Screenshot noch in einer automatisierten Prüfung.

Die Leiste, die diese zwei Regeln schützen, ist das Projektverzeichnis, und die Builds dahinter sind die zehn Konzept-Websites.

hero-swipe-lock.js:34-41

Was reduce nicht abschalten darf

Der Test lautet nicht: bewegt es sich. Eine Scroll-Sperre bewegt nichts, und sie unter reduce abzuschalten, würde eine Navigationshilfe zerstören, um die der Besucher ausdrücklich gebeten hat; ein Pin bewegt nichts mehr, wenn die Einstellung gelesen wird, und ihn stehen zu lassen, setzt die Leserin fest. Diese Linie pro Mechanismus zu ziehen — auch mitten durch eine Komponente, wenn dort die Naht verläuft — ist der größte Teil der Arbeit.

eine Scroll-Sperre
Kein Bewegungssystem, also schaltet reduce sie nicht ab — Scrollen zu sperren ist eine Navigationshilfe, um die der Besucher ausdrücklich gebeten hat. Die zwei Label-Wechsel daneben sind Bewegung, und sie schrumpfen auf einen sofortigen Wechsel. Die Sperre selbst wird vom Scroll-Besitzer referenzgezählt, dieses Bedienelement und das Menü können also je eine halten, ohne die des anderen freizugeben.
ein Pin
Layout, also muss reduce ihn zurücknehmen, statt ihn nur nicht mehr zu animieren. Die committete Barrierefreiheits-Prüfung übernimmt eine Zusicherung, die die alte Website nie ausgeführt hat: Unter reduce, bei geöffnetem Menü, lassen 1.200 px Mausrad, ein Druck auf End und ein Druck auf PageDown die Scroll-Position unverändert, und Escape gibt sie frei und lässt die Seite ihr eigenes Ende erreichen.
Smooth Scrolling
Ein anderer Modus statt eines abgeschalteten. Auf der Website, die diese hier ersetzt hat, ließ der naheliegende Griff — den Smoother anzuhalten — die Seite messbar völlig unscrollbar zurück: 0 px vom Mausrad, 0 px von End. Deshalb pollte ihr Patch bis zu 600 Frames lang nach einer globalen Variable und zerstörte stattdessen die Instanz. Hier wird der Smoother unter reduce schlicht nie konstruiert, und das Scrollen ist nativ und vollständig.
ein Pause-Bedienelement
Abwesend statt wirkungslos. Das Zertifikats-Deck teilt sechs Karten im Takt von 2.200 ms mit 600 ms Austeilen aus, eine Runde dauert 13,2 s, und unter reduce baut es sich gar nicht erst auf — der Button, der es anhalten würde, wird also entfernt statt deaktiviert. Ein Bedienelement, das da ist und nichts tut, ist schlimmer als eines, das fehlt: Es kostet einen Tabstopp, einen zugänglichen Namen und einen Druck.
eine Farb-Überblendung
Der eine Effekt dieser Website, der runs deklariert. Nichts verschiebt, dreht oder skaliert sich, und das Argument ist gemessen und nicht behauptet — mit abgeschaltetem Effekt ging bei 390 px der eigene Weg des Kapitels auf null, und die Seite hielt über ihre ganze Länge eine flache Farbe. Reduzierte Bewegung ist auf einem großen Teil der Handys standardmäßig an, was die Arbeitsannahme dieses Repositorys ist und keine von ihm gemessene Zahl.
Fünf Mechanismen, und die Überlegung, die jeden einzelnen entscheidet. Drei der fünf sitzen in Komponenten, deren andere Hälfte die Einstellung sehr wohl beantwortet.

Den Scroll-Fall gibt es auch in der Bibliothek selbst, und dort hat er sich bewegt. Die hier gepinnte Version, 1.1.20, enthält überhaupt keinen Verweis auf die Query — sie nie zu konstruieren war also die einzige verfügbare Antwort. Aktuelles Lenis liefert respectReducedMotion standardmäßig aktiviert aus und bezieht eine dritte Position: die Instanz weiterlaufen lassen, mit auf eins zu eins gezwungener Interpolation, damit die Synchronisation von WebGL und DOM erhalten bleibt, und programmatische Scrolls sofort ausführen. Drei vertretbare Antworten auf eine Einstellung — das Taxonomie-Argument im Kleinen.

Ob sich ein Smoother überhaupt lohnt, ist eine eigene Messung, und die steht hier: Lenis gegen natives Scrollen, gemessen.

Die Antwort Sofortsprung hat für Anker eigene Folgen für Fokus und History — was ein weicher Ankerlink kaputt macht.

axe-core 4.11.0 · 144 scans · 2026-08-24

Wie du es beweist, und die 707, aus denen 5 wurden

Die Aussage über die ganze Seite besteht aus zwei Zusicherungen im selben Durchgang, und sie sind die These in ausführbarer Form. Mit gesetzter Einstellung hält kein Subsystem einen Frame — die Zahl der aktiven Registrierungen an der Frame-Uhr ist null, der Hero ist nicht registriert, keine Rive-Instanz läuft, die Partikel-Runtime ist geparkt. Und jede 2-D-Fläche trägt weiterhin ein ruhendes Bild: Kein Canvas der sieben kommt mit null gezeichneten Pixeln zurück.

Der Round-Trip zur Laufzeit ist die schwerere Hälfte, denn eine Einstellung kann sich mitten in der Sitzung ändern. reduce einzuschalten baut 26 der 41 ScrollTrigger ab und jeden Pin-Spacer mit ihnen — dorthin gehen die 3.203 px Dokumenthöhe; es auszuschalten baut sie wieder auf, ohne dass über die Runde ein Effekt leckt oder sich verdoppelt, und ohne dass in einer der beiden Richtungen etwas beschnitten bleibt. Der Scroll-Offset überlebt das nicht — 9.645 px, dann 8.004, dann 9.716 — und diese 71 px Drift schieben drei weitere Reveals unterhalb der Falz in die Menge der noch nicht ausgelösten.

Committete Prüfnamen unter tests/golden/expectations, mit einem Durchlauf über die Prüflisten gezählt. Das sind Zusicherungen im versionierten Baum, nicht das Ergebnis eines Laufs.
Prüfliste Prüfungen darin Nennen die Einstellung
certificate 123 7
webgl 72 5
projects 52 6
rive 48 3
particles 38 5
rest 20 7
accessibility 20 9
alle 17 Prüflisten 817 61

Das ist keine rhetorische Behauptung. Die veröffentlichte axe-core-Zahl dieser Website — 5 Verstöße, axe-core 4.11.0, 72 Dokumente in zwei Viewports, also 144 Durchläufe, gemessen am 2026-08-24 — trägt eine Bedingung, und die Bedingung ist tragend: prefers-reduced-motion: reduce, nachdem jedes Dokument von Anfang bis Ende gescrollt wurde. Ohne sie ausgeführt, meldete dasselbe Instrument auf derselben Website 707 schwerwiegende Farbkontrast-Knoten über 60 Routen. Die zwei Durchgänge decken unterschiedlich viele Routen ab, sie sind also dasselbe Werkzeug vor und nach einer korrigierten Bedingung und kein kontrollierter Vergleich.

Jeder einzelne dieser 707 war ein Element mitten im Einblenden. Axe rechnet ein teiltransparentes Element gegen das, was dahinter liegt, eine Farbe wird also gegen eine fast identische Farbe gemessen, und eine einblendende Überschrift kommt auf 1,02:1. Die Korrektur hat nichts entschuldigt: Zwei echte Fehler haben sie überlebt und wurden behoben — eine Fallstudien-Karte, die über 612 Knoten und alle 33 ihrer Dokumente mit 4,36:1 druckte, und ein 404-Wasserzeichen mit 1,25:1 — und die verbleibenden 5 sind ein Element auf dem Papier des Zertifikats, mit 1,33:1 dort, wo 3:1 gilt, namentlich und per Selektor über drei Routen und zwei Breiten erlaubt.

Die Website, die diese hier ersetzt hat, ist das Gegenbeispiel zu alldem, und ihre Zahlen sind Forensik jenes alten Builds, nicht dieses hier. Ein grep nach der Query über ihre Engine liefert null. Unter reduce lief ihr GL-Tick 51-mal in drei Sekunden gegen 52 ohne, ihre globale GSAP-Timeline blieb über alle 836 ihrer Reveals bei einem Time Scale von 1, ihre 61 ScrollTrigger liefen unverändert, und genau eine Sorte Rive-Canvas pausierte — der Nav-Hamburger, 70 davon auf der ganzen Website — während 520 andere weiterspielten. Die Behandlung, die es gab, war ein 106-Zeilen-Patch, der nach einer globalen Variable pollte, plus 24 Stylesheet-Blöcke in zwei zueinander unverträglichen Richtlinien. Es ist nicht so, dass es niemand versucht hätte. Es ist so, dass die Einstellung dort beantwortet wurde, wo es leicht war, und dort unbeantwortet blieb, wo die Bewegung tatsächlich saß.

Die Kontrast-Hälfte dieser Geschichte ist ein eigener Text — warum ein axe-Durchlauf einer animierten Website meist Fehlalarme meldet.

Fragen

Bedeutet prefers-reduced-motion gar keine Animation mehr?

Nein. Media Queries Level 5 definiert es als Erkennen des Wunsches nach weniger Bewegung auf der Seite, und MDN beschreibt die Aufgabe der Autorin als ein Interface, das bewegungsbasierte Animationen entfernt, reduziert oder ersetzt — drei Möglichkeiten statt einer. WebKits eigene Vorstellung des Features im Jahr 2017 empfahl, eine alternative, einfachere Animation auszuliefern oder einen anderen visuellen Hinweis, der die gemeinte Bedeutung transportiert. Auf dieser Website werden daraus vier deklarierte Antworten pro Effekt, und die eine, die unverändert läuft, musste schriftlich begründet werden.

Sollte ein WebGL-Hero unter reduzierter Bewegung komplett entfernt werden?

Nein, und das ist der zweite Fehlerfall, nicht die sichere Variante. Unter reduce rendert der Hero dieser Website einen vollständigen Frame und hält dann an: Porträt, Halbton-Raster und angedockte Pose sind da, Pointer-Parallaxe und Fluid-Spur nicht, und keine Frame-Registrierung bleibt bestehen. Die committeten Prüfungen sichern zu, dass mehr als die halbe Canvas-Fläche beleuchtet ist, dass der Frame ein ganzer Frame ist — eine fixierte Zeit von 3 Sekunden muss das eigene Uniform des Shaders erreichen, denn ein halber Frame lässt es bei 0 mit einer Rausch-Textur auf null — dass kein Pointer registriert ist und dass der Renderer über die folgenden 1.200 ms nicht von selbst weitertickt.

Reicht die 0.01ms-Pauschalregel für sich allein?

Sie ist notwendig und wird hier ausgeliefert, im ersten Cascade Layer, wo ihre important-Deklaration nicht zu schlagen ist. Sie beantwortet eine der vier Fragen, die die Einstellung stellt. Sie kann nicht entscheiden, auf welchem Frame eine gestoppte Animation landet: Den 18-Sekunden-Wireframe-Zyklus dieser Website auf 0.01ms zu stauchen, lässt jeden Strich auf dem gelöschten Keyframe landen, und nur animation-name: none behebt das. Sie erreicht kein Layout, wo eine sticky-Platte weiterhin pinnt und eine horizontale Bahn weiterhin beschneidet, und sie erreicht nichts, was GSAP, Three.js, Rive oder ein Scroll-Smoother zeichnet.

Warum 0.01ms statt 0s, und wozu die Iterationszahl?

0.01ms erhält die Events transitionend und animationend, sodass JavaScript, das auf eine Transition wartet, bevor es ein Element entfernt oder eine Zustandsmaschine weiterschaltet, weiter funktioniert — eine echte Null kann das Event überspringen und diese Logik hängen lassen. Die Iterationszahl auf 1 zu setzen ist nötig, weil eine als infinite deklarierte Animation bei einer Dauer von 0.01ms nicht stehen bleibt: Sie wird zu einem sehr schnellen Flackern, dem Gegenteil der Bitte. Beide Zeilen gehören in den Ausschnitt, und keine von beiden sagt etwas über den Frame, auf dem die Animation stehen bleibt.

Erledigen GSAP oder Lenis das für mich?

GSAP nicht, und zwar mit Absicht: Die Dokumentation sagt, dass das Respektieren der Einstellung bewussten Code verlangt, und bietet gsap.matchMedia mit einer reduce-Bedingung an, deren eigenes Beispiel die Dauer auf null setzt. Lenis inzwischen schon — neuere Versionen berücksichtigen die Einstellung standardmäßig und schalten die Glättung ab, indem sie die Interpolation auf eins zu eins zwingen, während die Instanz am Leben bleibt, damit die Synchronisation von WebGL und DOM erhalten bleibt. Die in diesem Repository gepinnte Version, 1.1.20, enthält überhaupt keinen Verweis auf die Query — deshalb lautet die Antwort dieser Website, den Smoother unter reduce nie zu konstruieren. Prüfe deinen Pin, bevor du dich auf die Bibliothek verlässt.

Quellen

In diesem Repository gemessen

  • src/runtime/webgl/core/webgl-runtime.js Das Laufprädikat des Heros, der bei Delta null gerenderte ruhende Frame und die Notiz dazu, warum sich der Drawing Buffer nicht nachträglich screenshotten lässt.
  • src/styles/base/reset.css Der Notausschalter selbst, im ersten Cascade Layer, mit der dokumentierten Ausnahme an der Duration-Zeile.
  • src/runtime/rive/rive-instance.js Die settle-Antwort als Contract-Daten und die zehn festen Schritte, die sie erreichen, ohne sich an einer schleifenden Zustandsmaschine festzudrehen.
  • tools/golden/webgl.mjs Der reduce-Abschnitt: eine Zeit fixieren, einmal rendern, Pixel zurücklesen und beides zusichern — dass der Frame ganz ist und dass kein Frame gehalten wird.
  • contracts/published-measurements.json Die axe-core-Zahl mit ihrer Bedingung und die Notiz, die festhält, was derselbe Durchlauf ohne gesetzte Einstellung meldete.

Abgeglichen mit

Alaa Abbod

Geschrieben von

Alaa Abbod

Creative Developer — Herne, Deutschland

Designer und Entwickler, der barrierefreie Websites, Mobile Apps, Onlineshops und visuelle Identitäten als eine einzige Aufgabe von Hand baut. Diese Website erscheint in Englisch, Deutsch und Arabisch aus einer Quelle — daher kommen die meisten dieser Fragen.

Bitte dreh dein Gerät,
Diese Seite ist hochkant gebaut.