Back

Scroll-bewusste Komponenten in React entwickeln

Scroll-bewusste Komponenten in React entwickeln

Ihr Scroll-Handler wird 60 Mal pro Sekunde ausgelöst. Ihre Komponente rendert bei jedem Tick neu. Ihre Nutzer sehen Ruckeln statt flüssiger Animationen. Dieses Muster bringt React-Anwendungen schneller zum Absturz als fast jeder andere Performance-Fehler.

Die Entwicklung scroll-bewusster Komponenten in React erfordert ein Verständnis dafür, wann man Reacts State-System komplett vermeiden sollte. Dieser Artikel behandelt die richtigen Werkzeuge für React Scroll Position Tracking, warum Intersection Observer in React Ihre Standardwahl sein sollte und wie Sie performantes Scroll-Handling implementieren, wenn Sie tatsächlich rohe Scroll-Daten benötigen.

Wichtigste Erkenntnisse

  • Intersection Observer bewältigt die meisten scroll-bewussten Muster ohne Performance-Kosten
  • Wenn Sie kontinuierliche Scroll-Werte benötigen, verwenden Sie Refs und requestAnimationFrame anstelle von State
  • Reservieren Sie useSyncExternalStore für gemeinsam genutzten Scroll-State über Komponenten hinweg
  • Testen Sie mit Framework-Routing und bieten Sie immer Alternativen für reduzierte Bewegung an

Warum Intersection Observer Ihre erste Wahl sein sollte

Die meisten scroll-gesteuerten UI-React-Muster benötigen eigentlich keine Scroll-Position. Sie benötigen Sichtbarkeitserkennung: Ist dieses Element auf dem Bildschirm? Hat der Nutzer an diesem Abschnitt vorbeigescrollt?

Intersection Observer bewältigt diese Fälle, ohne bei jedem Scroll-Event Anwendungscode auszuführen. Der Browser optimiert die Beobachtung intern, was es dramatisch performanter macht als Scroll-Listener.

import { useInView } from 'react-intersection-observer';

function FadeInSection({ children }) {
  const { ref, inView } = useInView({
    threshold: 0.1,
    triggerOnce: true,
  });

  return (
    <div ref={ref} className={inView ? 'visible' : 'hidden'}>
      {children}
    </div>
  );
}

Die react-intersection-observer-Bibliothek umschließt diese API sauber. Verwenden Sie sie für Abschnitts-Tracking, Lazy Loading und Einblend-Animationen.

Wann Sie tatsächlich die Scroll-Position benötigen

Einige Muster erfordern kontinuierliche Scroll-Werte: Parallax-Effekte, Fortschrittsanzeigen oder scroll-gebundene Transformationen. Hier versagen die meisten Tutorials.

Machen Sie das niemals:

// This causes re-renders on every scroll tick
const [scrollY, setScrollY] = useState(0);

useEffect(() => {
  const handleScroll = () => setScrollY(window.scrollY);
  window.addEventListener('scroll', handleScroll);
  return () => window.removeEventListener('scroll', handleScroll);
}, []);

Dieses Muster löst Reacts Reconciliation-Prozess mehr als 60 Mal pro Sekunde während aktivem Scrollen aus.

Das Ref-basierte Muster

Speichern Sie Scroll-Werte in Refs und aktualisieren Sie das DOM direkt:

import { useRef, useEffect } from 'react';

function ParallaxElement() {
  const elementRef = useRef(null);
  const rafId = useRef(null);

  useEffect(() => {
    const handleScroll = () => {
      if (rafId.current) return;
      
      rafId.current = requestAnimationFrame(() => {
        if (elementRef.current) {
          const offset = window.scrollY * 0.5;
          elementRef.current.style.transform = `translateY(${offset}px)`;
        }
        rafId.current = null;
      });
    };

    window.addEventListener('scroll', handleScroll, { passive: true });
    return () => {
      window.removeEventListener('scroll', handleScroll);
      if (rafId.current) cancelAnimationFrame(rafId.current);
    };
  }, []);

  return <div ref={elementRef}>Parallax content</div>;
}

Wichtige Details: requestAnimationFrame drosselt Updates auf die Display-Bildwiederholrate. Die Option { passive: true } teilt dem Browser mit, dass Sie preventDefault() nicht aufrufen werden, was Scroll-Optimierungen ermöglicht.

useSyncExternalStore für gemeinsam genutzten Scroll-State

Wenn mehrere Komponenten die Scroll-Position benötigen, behandeln Sie sie als externen Store:

import { useSyncExternalStore } from 'react';

const scrollStore = {
  subscribe: (callback) => {
    window.addEventListener('scroll', callback, { passive: true });
    return () => window.removeEventListener('scroll', callback);
  },
  getSnapshot: () => window.scrollY,
  getServerSnapshot: () => 0,
};

function useScrollPosition() {
  return useSyncExternalStore(
    scrollStore.subscribe,
    scrollStore.getSnapshot,
    scrollStore.getServerSnapshot
  );
}

Dieses Muster funktioniert korrekt mit Reacts Concurrent-Features und handhabt Server-seitiges Rendering. In der Praxis sollten Sie Updates dennoch drosseln oder grobkörniger gestalten (zum Beispiel durch Ausgabe von Abschnittsänderungen anstelle von rohen Pixeln), um Neu-Rendering bei jedem Scroll-Frame zu vermeiden.

useLayoutEffect vs useEffect für Scroll

Verwenden Sie useLayoutEffect, wenn Sie DOM-Elemente messen müssen, bevor der Browser zeichnet. Dies verhindert visuelles Flackern bei der Berechnung von Positionen mit getBoundingClientRect().

Verwenden Sie useEffect zum Anhängen von Scroll-Listenern. Das Listener-Setup muss das Zeichnen nicht blockieren.

Framework-Überlegungen

Next.js App Router und ähnliche Frameworks verwalten die Scroll-Wiederherstellung automatisch. Benutzerdefinierte Scroll-Handler können mit diesem Verhalten in Konflikt geraten. Testen Sie scroll-bewusste Komponenten nach client-seitiger Navigation, um Wiederherstellungsprobleme zu erkennen.

CSS Scroll-Driven Animations

Moderne Browser unterstützen animation-timeline: scroll() für reine CSS-Scroll-Effekte. Die Browser-Unterstützung bleibt unvollständig, aber dieser Ansatz eliminiert JavaScript vollständig für unterstützte Fälle. Verwenden Sie ihn mit progressiver Verbesserung – bieten Sie eine Fallback-Erfahrung für nicht unterstützte Browser.

Barrierefreiheit

Respektieren Sie immer prefers-reduced-motion. Umschließen Sie scroll-ausgelöste Animationen:

const prefersReducedMotion =
  typeof window !== 'undefined' &&
  window.matchMedia('(prefers-reduced-motion: reduce)').matches;

Überspringen Sie Animationen oder bieten Sie sofortige Übergänge an, wenn dies true zurückgibt.

Fazit

Scroll-bewusste Komponenten müssen die Performance Ihrer React-Anwendung nicht beeinträchtigen. Der Schlüssel liegt darin, das richtige Werkzeug für jeden Anwendungsfall zu wählen. Greifen Sie zuerst zu Intersection Observer – er bewältigt Sichtbarkeitserkennung ohne Performance-Overhead. Wenn Sie tatsächlich kontinuierliche Scroll-Werte benötigen, umgehen Sie Reacts State-System, indem Sie Refs und requestAnimationFrame für direkte DOM-Manipulation verwenden. Für gemeinsam genutzten Scroll-State über mehrere Komponenten hinweg bietet useSyncExternalStore ein sauberes, concurrent-sicheres Muster. Welchen Ansatz Sie auch wählen, testen Sie gründlich mit dem Routing Ihres Frameworks und respektieren Sie immer die Nutzerpräferenzen für reduzierte Bewegung.

FAQs

Das Setzen von State bei jedem Scroll-Event löst Reacts Reconciliation-Prozess mehr als 60 Mal pro Sekunde aus. Jedes State-Update verursacht ein Neu-Rendering, was bedeutet, dass React das virtuelle DOM vergleichen und potenziell das echte DOM wiederholt aktualisieren muss. Dies überlastet den Main Thread und verursacht sichtbares Ruckeln. Die Verwendung von Refs umgeht dies vollständig, da Ref-Updates keine Neu-Renderings auslösen.

Verwenden Sie useSyncExternalStore, wenn mehrere Komponenten denselben Scroll-Positionswert benötigen. Es integriert sich ordnungsgemäß mit Reacts Concurrent-Rendering-Features und behandelt Randfälle wie Tearing, bei dem verschiedene Komponenten während eines Renderings unterschiedliche Werte sehen könnten. Es bietet auch integrierte Unterstützung für server-seitiges Rendering durch getServerSnapshot.

Sie können sie mit progressiver Verbesserung verwenden. CSS Scroll-Driven Animations funktionieren derzeit in Chrome und Edge, wobei die Unterstützung in Safari und Firefox noch unvollständig ist oder in Arbeit. Implementieren Sie sie als Upgrade für unterstützte Browser und bieten Sie gleichzeitig ein JavaScript-Fallback oder eine vereinfachte Erfahrung für andere. Prüfen Sie caniuse.com für aktuelle Browser-Unterstützung, bevor Sie sich auf sie verlassen.

Testen Sie nach client-seitiger Navigation, nicht nur beim initialen Seitenladen. Der App Router verwaltet die Scroll-Wiederherstellung automatisch, was mit benutzerdefinierten Scroll-Handlern in Konflikt geraten kann. Navigieren Sie zwischen Seiten mit Link-Komponenten und überprüfen Sie, ob Ihre Scroll-Listener korrekt neu angehängt werden und nicht mit dem Scroll-Positions-Wiederherstellungsverhalten des Frameworks interferieren.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay