Start · Sprachen · JavaScript · Referenz · Document: scroll

Document: scroll

Ereignis

Das <code>scroll</code>-Ereignis wird am <code>Document</code> ausgelöst, wenn die Dokumentansicht (der Viewport) gescrollt wurde.

seit JavaScript Baseline: alle Browser Kategorie: webapi

Signatur

document.addEventListener("scroll", handler)

Beschreibung

Das scroll-Ereignis wird ausgelöst, sobald sich die Scrollposition der Dokumentansicht ändert – egal ob per Mausrad, Touch-Geste, Tastatur, Scrollbalken oder per Skript (z. B. window.scrollTo()). Du registrierst es mit document.addEventListener("scroll", handler) oder über die Eigenschaft document.onscroll. Das Ereignis ist nicht abbrechbar (cancelable: false) und bubbelt am Dokument nur bis zum Window; es handelt sich um ein einfaches Event.

Typische Einsatzzwecke sind Fortschrittsanzeigen beim Lesen, „Nach oben“-Schaltflächen, klebende Kopfzeilen oder das Nachladen von Inhalten (Endlos-Scrollen). Die aktuelle Position liest du über window.scrollY bzw. document.documentElement.scrollTop aus.

Wichtig: Scroll-Ereignisse können sehr häufig feuern. Browser bündeln sie zwar ungefähr auf Frame-Takt, aufwendige Arbeit im Handler (Layout-Abfragen, DOM-Änderungen) führt trotzdem schnell zu Ruckeln. Drossele die Verarbeitung deshalb mit requestAnimationFrame() oder einem Throttle und registriere den Listener mit { passive: true }. Für Sichtbarkeitsprüfungen ist der IntersectionObserver meist die bessere Wahl.

Das Scrollen einzelner Elemente mit eigenem Überlauf (overflow: auto) löst scroll am jeweiligen Element aus, nicht am Dokument – dieses Ereignis bubbelt nicht zum document. Wenn du alle Scrollvorgänge erfassen willst, nutze die Capture-Phase.

Parameter

Name Typ Default Beschreibung
$type Pflicht string Der Ereignisname "scroll".
$handler Pflicht (event: Event) => void Callback, der bei jedem Scrollvorgang ein generisches Event-Objekt erhält (event.target ist das document).
$options boolean | { capture?: boolean, once?: boolean, passive?: boolean, signal?: AbortSignal } false Optionen für addEventListener. Empfohlen: { passive: true }. Mit capture: true werden auch Scroll-Ereignisse einzelner Elemente abgefangen.

Rückgabewert

Typ
void
Beschreibung
addEventListener liefert undefined. Der Handler erhält ein Event ohne zusätzliche Eigenschaften; die Position liest du über window.scrollX/window.scrollY aus.

Beispiele

Lesefortschritt mit requestAnimationFrame drosseln

const bar = document.querySelector('#progress');
let ticking = false;

const update = () => {
  const max = document.documentElement.scrollHeight - window.innerHeight;
  const percent = max > 0 ? (window.scrollY / max) * 100 : 0;
  bar.style.width = `${percent.toFixed(1)}%`;
  ticking = false;
};

document.addEventListener('scroll', () => {
  if (!ticking) {
    requestAnimationFrame(update);
    ticking = true;
  }
}, { passive: true });

„Nach oben“-Button per onscroll ein- und ausblenden

const topButton = document.querySelector('#to-top');

document.onscroll = () => {
  topButton.hidden = window.scrollY < 400;
};

topButton.addEventListener('click', () => {
  window.scrollTo({ top: 0, behavior: 'smooth' });
});

Endlos-Scrollen mit sauberem Entfernen des Listeners

let page = 1;
let loading = false;

const onScroll = async () => {
  const nearBottom = window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 200;
  if (!nearBottom || loading) return;
  loading = true;
  const res = await fetch(`/api/artikel?page=${++page}`);
  const items = await res.json();
  if (items.length === 0) {
    // Keine weiteren Daten: Listener entfernen
    document.removeEventListener('scroll', onScroll);
    console.log('Alle Artikel geladen');
  }
  loading = false;
};

document.addEventListener('scroll', onScroll, { passive: true });
Alle Artikel geladen

Alle Scrollvorgänge (auch Elemente) in der Capture-Phase erfassen

const controller = new AbortController();

document.addEventListener('scroll', (event) => {
  const quelle = event.target === document ? 'Dokument' : event.target.id || event.target.tagName;
  console.log(`Gescrollt: ${quelle}`);
}, { capture: true, passive: true, signal: controller.signal });

// Später alle Listener auf einmal entfernen
// controller.abort();
Gescrollt: Dokument Gescrollt: sidebar

// Wichtig · Fallstricke

  • Performance: Keine teuren Layout-Abfragen (getBoundingClientRect(), offsetTop) ungedrosselt im Handler ausführen – mit requestAnimationFrame() oder Throttle bündeln. Für Sichtbarkeit lieber IntersectionObserver verwenden.
  • Nicht abbrechbar: event.preventDefault() verhindert das Scrollen nicht. Um Scrollen zu unterbinden, müsstest du wheel/touchmove (nicht passiv) abfangen oder overflow: hidden setzen.
  • Kein Bubbling von Elementen: Scrollt ein Container mit eigenem Überlauf, erreicht das Ereignis das document nur in der Capture-Phase.
  • Das Ereignis feuert auch bei programmatischem Scrollen und beim Wiederherstellen der Scrollposition nach dem Neuladen.
  • Für das Ende einer Scrollbewegung gibt es das neuere Ereignis scrollend (Chrome 114+, Firefox 109+, Safari erst ab 26 bzw. noch eingeschränkt) – ggf. Fallback mit Timeout.
  • Kompatibilität: scroll am Document wird von allen Browsern unterstützt; die Option passive ab Chrome 51, Firefox 49, Safari 10.