Signatur
Beschreibung
Das scrollend-Ereignis des VisualViewport-Interfaces wird ausgelöst, wenn eine Scroll-Bewegung des visuellen Viewports endet. Der visuelle Viewport ist der tatsächlich sichtbare Bildschirmausschnitt. Auf Mobilgeräten kann er sich gegenüber dem Layout-Viewport verschieben, etwa wenn der Nutzer hineingezoomt hat und den Ausschnitt mit dem Finger bewegt oder wenn die Bildschirmtastatur erscheint.
Das Ereignis feuert erst, wenn alle Scroll-Updates fertig sind: Die Touch- bzw. Zeigerinteraktion ist beendet und auch eine ausklingende Bewegung (Momentum) oder ein weiches Scrollen (Smooth Scrolling) ist vorbei. Damit brauchst du für „Scrollen ist vorbei“ keine eigene Entprellung mit setTimeout auf das scroll-Ereignis mehr.
Typische Anwendungen: Fest positionierte Elemente wie Werkzeugleisten oder Hinweis-Banner nach dem Verschieben des Ausschnitts neu an offsetLeft/offsetTop ausrichten, Positionsdaten sparsam protokollieren oder aufwendige Neuberechnungen erst nach Ende der Bewegung ausführen.
Den Handler registrierst du mit visualViewport.addEventListener("scrollend", handler) oder über die Eigenschaft visualViewport.onscrollend. Das Ereignis ist ein einfaches Event, das nicht abgebrochen werden kann und nicht nach oben weitergereicht wird (kein Bubbling).
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $type Pflicht | string | Der Ereignistyp, hier "scrollend". |
|
| $handler Pflicht | (event: Event) => void | EventListenerObject | Funktion, die nach Ende der Scroll-Bewegung aufgerufen wird. event.target ist das VisualViewport-Objekt. |
|
| $options | boolean | AddEventListenerOptions | false | Optionen wie once, passive oder signal (AbortSignal zum Entfernen des Handlers). |
Beispiele
Endposition nach dem Verschieben protokollieren
const vv = window.visualViewport;
if (vv) {
vv.addEventListener('scrollend', () => {
console.log(`Scroll beendet: offset=(${vv.offsetLeft.toFixed(0)}, ${vv.offsetTop.toFixed(0)}), scale=${vv.scale}`);
});
}
Werkzeugleiste nach dem Scrollen am sichtbaren Ausschnitt ausrichten
const toolbar = document.querySelector('#toolbar');
const vv = window.visualViewport;
const placeToolbar = () => {
// Leiste unten im sichtbaren Bereich halten, Zoom ausgleichen
const x = vv.offsetLeft;
const y = vv.offsetTop + vv.height - toolbar.offsetHeight / vv.scale;
toolbar.style.transform = `translate(${x}px, ${y}px) scale(${1 / vv.scale})`;
toolbar.hidden = false;
};
// Während der Bewegung ausblenden, danach neu platzieren
vv.addEventListener('scroll', () => { toolbar.hidden = true; }, { passive: true });
vv.addEventListener('scrollend', placeToolbar);
vv.addEventListener('resize', placeToolbar);
on-Property verwenden und Handler wieder entfernen
const vv = window.visualViewport;
// Variante 1: on-Property
vv.onscrollend = () => console.log('scrollend über onscrollend');
// Entfernen:
vv.onscrollend = null;
// Variante 2: addEventListener mit AbortController
const controller = new AbortController();
vv.addEventListener('scrollend', () => console.log('scrollend'), { signal: controller.signal });
// Später, z. B. beim Verlassen einer Ansicht:
controller.abort();
Feature-Erkennung mit Ersatzlösung per Entprellung
const vv = window.visualViewport;
const onEnd = () => console.log('Viewport-Scroll beendet');
if (vv && 'onscrollend' in vv) {
vv.addEventListener('scrollend', onEnd);
} else if (vv) {
// Ersatz für Browser ohne scrollend (z. B. Safari)
let timer;
vv.addEventListener('scroll', () => {
clearTimeout(timer);
timer = setTimeout(onEnd, 150);
}, { passive: true });
}
// Wichtig · Fallstricke
- Browser-Unterstützung: Chromium-Browser ab Version 114 und Firefox ab 109 unterstützen das Ereignis. Safari unterstützt
scrollendderzeit nicht. Prüfe deshalb vorher mit'onscrollend' in visualViewportund sieh eine Ersatzlösung per Entprellung vor. - Visueller und Layout-Viewport:
visualViewportmeldet nur Verschiebungen des sichtbaren Ausschnitts, etwa beim Zoomen oder durch die Bildschirmtastatur. Normales Scrollen des Dokuments ohne Zoom löst meist dasscrollend-Ereignis vonwindow/documentaus. Für die Seite selbst registrierst du es also dort. - Kein Ereignis ohne Bewegung: Ändert sich die Position nicht, etwa weil der Nutzer schon am Rand ist oder ein
scrollTo()zur aktuellen Position führt, feuertscrollendnicht. - Zoom-Änderungen: Reine Größen- oder Zoom-Änderungen lösen
resizeaus, nichtscrollend. Registriere für eine zuverlässige Positionierung beide Ereignisse. visualViewportkann in manchen Umgebungen (ältere Browser, bestimmte WebViews)nullbzw.undefinedsein. Greife deshalb nur nach einer Prüfung darauf zu.- Das Ereignis wird nicht nach oben weitergereicht und lässt sich nicht abbrechen.
preventDefault()bewirkt nichts.