Signatur
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
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 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();
// Wichtig · Fallstricke
- Performance: Keine teuren Layout-Abfragen (
getBoundingClientRect(),offsetTop) ungedrosselt im Handler ausführen – mitrequestAnimationFrame()oder Throttle bündeln. Für Sichtbarkeit lieberIntersectionObserververwenden. - Nicht abbrechbar:
event.preventDefault()verhindert das Scrollen nicht. Um Scrollen zu unterbinden, müsstest duwheel/touchmove(nicht passiv) abfangen oderoverflow: hiddensetzen. - Kein Bubbling von Elementen: Scrollt ein Container mit eigenem Überlauf, erreicht das Ereignis das
documentnur 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:
scrollamDocumentwird von allen Browsern unterstützt; die Optionpassiveab Chrome 51, Firefox 49, Safari 10.