Start · Sprachen · JavaScript · Referenz · Element: scroll

Element: scroll

Ereignis

Das <code>scroll</code>-Ereignis wird ausgelöst, wenn der Inhalt eines scrollbaren Elements gescrollt wurde.

seit JavaScript Baseline: alle Browser Kategorie: webapi

Signatur

element.addEventListener("scroll", handler)

Beschreibung

Das scroll-Ereignis wird an einem Element ausgelöst, sobald sich dessen Scrollposition (scrollTop oder scrollLeft) geändert hat – egal ob durch Mausrad, Touch-Geste, Tastatur, Scrollleiste oder per Skript (scrollTo(), scrollIntoView()). Registrieren lässt sich ein Handler über element.addEventListener("scroll", handler) oder über die Eigenschaft element.onscroll = handler.

Typische Einsatzfälle sind Fortschrittsbalken beim Lesen, „Nach oben"-Schaltflächen, Endlos-Scrollen in Listen, das Synchronisieren zweier Scrollbereiche oder das Ein- und Ausblenden von Kopfzeilen. Das Ereignis ist ein einfaches Event ohne zusätzliche Eigenschaften; die aktuelle Position liest man direkt am Element aus.

Wichtig: Am Element steigt scroll nicht auf (kein Bubbling) und lässt sich nicht abbrechen – preventDefault() hat keine Wirkung. Für das Scrollen der ganzen Seite lauscht man auf document bzw. window. Da das Ereignis sehr häufig feuern kann, sollte der Handler schlank bleiben oder per requestAnimationFrame gedrosselt werden.

Parameter

Name Typ Default Beschreibung
$type Pflicht string Der Ereignisname "scroll".
$handler Pflicht (event: Event) => void Callback, der bei jeder Scrollpositionsänderung aufgerufen wird. event.target ist das gescrollte Element.
$options boolean | { capture?: boolean, once?: boolean, passive?: boolean, signal?: AbortSignal } false Optionen für addEventListener. passive: true signalisiert, dass kein preventDefault() aufgerufen wird; signal erlaubt das Entfernen per AbortController.

Beispiele

Scrollposition eines Containers anzeigen

const box = document.querySelector('#log');
const anzeige = document.querySelector('#position');

box.addEventListener('scroll', (event) => {
  const el = event.target;
  anzeige.textContent = `oben: ${Math.round(el.scrollTop)} px`;
}, { passive: true });
oben: 120 px (aktualisiert beim Scrollen)

Lesefortschritt mit requestAnimationFrame drosseln

const artikel = document.querySelector('.artikel');
const balken = document.querySelector('.fortschritt');
let geplant = false;

const aktualisieren = () => {
  const max = artikel.scrollHeight - artikel.clientHeight;
  const prozent = max > 0 ? (artikel.scrollTop / max) * 100 : 0;
  balken.style.width = `${prozent}%`;
  geplant = false;
};

artikel.addEventListener('scroll', () => {
  if (!geplant) {
    geplant = true;
    requestAnimationFrame(aktualisieren);
  }
}, { passive: true });

Endlos-Liste: nachladen und Listener wieder entfernen

const liste = document.querySelector('#produkte');
let seite = 1;

const beiScroll = async () => {
  const nahAmEnde = liste.scrollTop + liste.clientHeight >= liste.scrollHeight - 50;
  if (!nahAmEnde) return;

  liste.removeEventListener('scroll', beiScroll);
  const res = await fetch(`/api/produkte?seite=${++seite}`);
  const daten = await res.json();
  for (const p of daten.items) {
    const li = document.createElement('li');
    li.textContent = p.name;
    liste.append(li);
  }
  // Nur weiter lauschen, wenn es noch Daten gibt
  if (daten.hatMehr) liste.addEventListener('scroll', beiScroll);
};

liste.addEventListener('scroll', beiScroll);

Alternative: onscroll-Property und Scrollbereiche synchronisieren

const links = document.querySelector('#code-alt');
const rechts = document.querySelector('#code-neu');

links.onscroll = () => {
  rechts.scrollTop = links.scrollTop;
};

// Handler wieder entfernen
// links.onscroll = null;

// Wichtig · Fallstricke

  • Kein Bubbling: Das Ereignis steigt an Elementen nicht auf. Wer per Delegation auf mehrere Container lauschen will, muss { capture: true } auf einem Vorfahren verwenden.
  • Nur scrollbare Elemente: Das Element braucht overflow: auto/scroll und überlaufenden Inhalt, sonst feuert nichts. Für das Seiten-Scrollen document bzw. window verwenden – nicht document.body.
  • Performance: Das Ereignis kann pro Frame feuern. Teure Layout-Abfragen (z. B. getBoundingClientRect()) mit requestAnimationFrame bündeln und passive: true setzen. Für Sichtbarkeitsprüfungen (Lazy Loading, Endlos-Scrollen) ist IntersectionObserver meist die bessere Wahl.
  • Nicht abbrechbar: preventDefault() verhindert das Scrollen nicht; dazu müsste man wheel/touchmove (nicht passiv) abfangen oder CSS overflow: hidden nutzen.
  • Auch programmatisches Scrollen löst das Ereignis aus – Vorsicht vor Endlosschleifen beim gegenseitigen Synchronisieren zweier Bereiche.
  • Für das Ende einer Scrollbewegung gibt es das neuere Ereignis scrollend (nicht in allen Browsern, Safari erst neuere Versionen).