Signatur
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 });
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/scrollund überlaufenden Inhalt, sonst feuert nichts. Für das Seiten-Scrollendocumentbzw.windowverwenden – nichtdocument.body. - Performance: Das Ereignis kann pro Frame feuern. Teure Layout-Abfragen (z. B.
getBoundingClientRect()) mitrequestAnimationFramebündeln undpassive: truesetzen. Für Sichtbarkeitsprüfungen (Lazy Loading, Endlos-Scrollen) istIntersectionObservermeist die bessere Wahl. - Nicht abbrechbar:
preventDefault()verhindert das Scrollen nicht; dazu müsste manwheel/touchmove(nicht passiv) abfangen oder CSSoverflow: hiddennutzen. - 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).