Signatur
Beschreibung
Das scroll-Ereignis der Schnittstelle VisualViewport wird ausgelöst, sobald sich der sichtbare Ausschnitt gegenüber dem Layout-Viewport verschiebt. Die Werte offsetLeft/offsetTop bzw. pageLeft/pageTop ändern sich dabei. Typisch ist das auf Mobilgeräten: Zoomt man per Pinch-Geste hinein und wischt dann, bewegt sich nur das visuelle Viewport, das Layout-Viewport bleibt, wo es ist. Deshalb feuert dann oft kein window-scroll-Ereignis.
Man registriert den Handler mit window.visualViewport.addEventListener("scroll", handler) oder über die Eigenschaft window.visualViewport.onscroll = handler. Das Ereignis ist ein einfaches Event: Es ist nicht abbrechbar und steigt nicht auf (kein Bubbling). Die aktuellen Werte liest man direkt aus event.target bzw. window.visualViewport aus.
Wofür man es braucht: Elemente sollen trotz Zoom sichtbar bleiben, z. B. fixierte Werkzeugleisten, Kontextmenüs oder eigene Tooltips. Auch Leisten über der Bildschirmtastatur lassen sich damit ausrichten. Meist wird das Ereignis zusammen mit dem resize-Ereignis von VisualViewport verwendet, weil Zoomen und das Ein- oder Ausblenden der Tastatur die Größe verändern und nicht die Position.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $type Pflicht | string | Der Ereignistyp, hier immer "scroll". |
|
| $handler Pflicht | (event: Event) => void | Callback, der bei jeder Verschiebung aufgerufen wird. event.target ist das VisualViewport-Objekt. |
|
| $options | boolean | AddEventListenerOptions | false | Optionen wie passive, once oder signal (zum Entfernen per AbortController). |
Rückgabewert
addEventListener gibt nichts zurück. Der Handler erhält ein Event-Objekt ohne zusätzliche Eigenschaften.Beispiele
Scroll-Position des visuellen Viewports protokollieren
const vv = window.visualViewport;
if (vv) {
vv.addEventListener('scroll', (event) => {
const { offsetLeft, offsetTop, pageLeft, pageTop, scale } = event.target;
console.log(`Offset: ${offsetLeft.toFixed(0)}/${offsetTop.toFixed(0)}, ` +
`Seite: ${pageLeft.toFixed(0)}/${pageTop.toFixed(0)}, Zoom: ${scale.toFixed(2)}`);
});
}
Werkzeugleiste trotz Pinch-Zoom sichtbar halten (scroll + resize)
const toolbar = document.querySelector('#toolbar');
const vv = window.visualViewport;
let pending = false;
const updateToolbar = () => {
if (pending) return;
pending = true;
requestAnimationFrame(() => {
pending = false;
// Leiste unten im sichtbaren Bereich ausrichten und 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.style.transformOrigin = '0 0';
});
};
if (vv) {
vv.addEventListener('scroll', updateToolbar);
vv.addEventListener('resize', updateToolbar);
updateToolbar();
}
on-Property verwenden und Listener wieder entfernen
const vv = window.visualViewport;
// Variante 1: Event-Handler-Eigenschaft
vv.onscroll = () => console.log('pageTop:', vv.pageTop);
// Entfernen:
vv.onscroll = null;
// Variante 2: benannte Funktion und removeEventListener
const onVvScroll = () => console.log('offsetTop:', vv.offsetTop);
vv.addEventListener('scroll', onVvScroll);
vv.removeEventListener('scroll', onVvScroll);
// Variante 3: mehrere Listener über AbortController entfernen
const controller = new AbortController();
vv.addEventListener('scroll', onVvScroll, { signal: controller.signal });
vv.addEventListener('resize', onVvScroll, { signal: controller.signal });
controller.abort(); // entfernt beide Listener
// Wichtig · Fallstricke
- Nicht gleich
window-scroll: Daswindow-Ereignis bezieht sich auf das Layout-Viewport. Scrollen innerhalb einer gezoomten Ansicht löst oft nur dasVisualViewport-scrollaus. Umgekehrt feuern bei normalem Scrollen je nach Browser beide Ereignisse oder nur eines. Wer beide Fälle abdecken will, sollte auf beide hören. - Größenänderungen: Beim Zoomen und beim Ein- oder Ausblenden der Bildschirmtastatur ändert sich vor allem die Größe. Dafür gibt es das
resize-Ereignis, das man fast immer zusätzlich registriert. - Leistung: Das Ereignis kann sehr oft feuern. Schreibzugriffe aufs DOM daher mit
requestAnimationFramebündeln, möglichst mittransformarbeiten und im Handler nicht abwechselnd lesen und schreiben (sonst entsteht Layout-Thrashing). - Kein Bubbling, nicht abbrechbar:
preventDefault()hat keine Wirkung. Das Scrollen lässt sich damit nicht verhindern. - Kompatibilität:
window.visualViewportkann in älteren Browsernundefinedsein (Firefox vor Version 91; auf Android gab es sie schon früher). Vor dem Zugriff also prüfen, z. B. mitif (window.visualViewport). Auf Desktop-Browsern ohne Pinch-Zoom feuert das Ereignis selten. - In Iframes beschreibt
visualViewportdas Viewport des Iframes.