Signatur
Beschreibung
Mit der CSS-Eigenschaft content-visibility: auto darf der Browser Layout und Zeichnen der Inhalte eines Elements überspringen, solange das Element für den Nutzer nicht relevant ist, also z. B. außerhalb des Viewports liegt und weder Fokus noch Textauswahl enthält. Das Ereignis contentvisibilityautostatechange meldet genau diesen Zustandswechsel: Es wird ausgelöst, wenn der Browser die Inhalte ab jetzt überspringt, und auch dann, wenn er sie wieder rendert.
Der Handler bekommt ein ContentVisibilityAutoStateChangeEvent. Dessen boolesche Eigenschaft skipped sagt dir, ob die Inhalte gerade übersprungen werden (true) oder wieder gerendert werden (false).
Typische Einsatzfälle:
- Arbeit pausieren, die niemand sieht: Canvas- oder WebGL-Animationen, Video-Vorschauen, Polling oder Timer.
- Inhalte nachladen oder aufbauen, sobald ein Abschnitt relevant wird, zum Beispiel Daten per
fetch()holen oder Diagramme zeichnen. - Ressourcen freigeben, wenn lange Listen oder Feeds aus dem sichtbaren Bereich gescrollt werden.
Du registrierst das Ereignis mit addEventListener("contentvisibilityautostatechange", …) oder, wo der Browser es unterstützt, über die Eigenschaft element.oncontentvisibilityautostatechange. Es lässt sich nicht abbrechen, und seine Standardaktion kann man nicht verhindern.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $type Pflicht | string | Der Ereignisname, immer "contentvisibilityautostatechange". |
|
| $handler Pflicht | (event: ContentVisibilityAutoStateChangeEvent) => void | Callback, der bei jedem Zustandswechsel aufgerufen wird. event.skipped ist true, wenn die Inhalte übersprungen werden, und false, wenn sie wieder gerendert werden. |
|
| $options | boolean | AddEventListenerOptions | false | Übliche Listener-Optionen wie once, signal (zum Entfernen per AbortController) oder capture. |
Rückgabewert
addEventListener() gibt nichts zurück. Der Handler erhält ein ContentVisibilityAutoStateChangeEvent mit der schreibgeschützten Eigenschaft skipped: boolean.Beispiele
Canvas-Animation pausieren, solange der Abschnitt übersprungen wird
// CSS: .chart-section { content-visibility: auto; contain-intrinsic-size: auto 400px; }
const section = document.querySelector('.chart-section');
const canvas = section.querySelector('canvas');
const ctx = canvas.getContext('2d');
let frameId = null;
let angle = 0;
const draw = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(100 + Math.cos(angle) * 50, 100 + Math.sin(angle) * 50, 10, 10);
angle += 0.05;
frameId = requestAnimationFrame(draw);
};
section.addEventListener('contentvisibilityautostatechange', (event) => {
if (event.skipped) {
cancelAnimationFrame(frameId);
frameId = null;
console.log('Animation pausiert');
} else if (frameId === null) {
frameId = requestAnimationFrame(draw);
console.log('Animation läuft');
}
});
Daten erst laden, wenn ein Abschnitt relevant wird
const sections = document.querySelectorAll('article.lazy');
const loadContent = async (event) => {
if (event.skipped) return;
const article = event.target;
// Listener entfernen, damit nur einmal geladen wird
article.removeEventListener('contentvisibilityautostatechange', loadContent);
const response = await fetch(`/api/artikel/${article.dataset.id}`);
const { html } = await response.json();
article.querySelector('.body').textContent = html;
console.log(`Artikel ${article.dataset.id} geladen`);
};
sections.forEach((article) => {
article.style.contentVisibility = 'auto';
article.style.containIntrinsicSize = 'auto 600px';
article.addEventListener('contentvisibilityautostatechange', loadContent);
});
on-Property und Entfernen per AbortController
const panel = document.querySelector('#feed');
const controller = new AbortController();
panel.addEventListener(
'contentvisibilityautostatechange',
({ skipped }) => console.log(skipped ? 'übersprungen' : 'gerendert'),
{ signal: controller.signal }
);
// Alternativ, sofern der Browser die Eigenschaft kennt:
if ('oncontentvisibilityautostatechange' in panel) {
panel.oncontentvisibilityautostatechange = (e) => console.log('skipped =', e.skipped);
}
// Später: Listener abmelden
controller.abort();
panel.oncontentvisibilityautostatechange = null;
// Wichtig · Fallstricke
- Nur bei
content-visibility: auto: Beivisibleoderhiddenwird das Ereignis nicht ausgelöst. Wenn du den Wert dynamisch änderst, bekommst du die Ereignisse nur, solangeautoaktiv ist. - Relevanz ist mehr als Sichtbarkeit: Ein Element bleibt auch außerhalb des Viewports relevant, wenn es Fokus oder eine Textauswahl enthält oder in der Top-Layer liegt. Dann ist
skippedweiterhinfalse. Wenn du nur wissen willst, ob ein Element sichtbar ist, nimm besser einenIntersectionObserver. - Asynchron: Der Browser löst das Ereignis im Rendering-Zyklus aus, also nicht synchron beim Scrollen. Direkt nach dem Laden der Seite kann es für den Anfangszustand einmal feuern. Verlass dich darauf aber nicht, sondern prüf den Zustand im Code selbst.
contain-intrinsic-sizesetzen: Ohne eine geschätzte Größe fallen übersprungene Elemente auf die Höhe 0 zusammen. Das lässt die Scrollleiste springen und führt zu vielen Zustandswechseln.- Listener direkt am Element registrieren: Verlass dich bei diesem Ereignis nicht auf Event-Delegation über einen Vorfahren.
- Browser-Kompatibilität: Unterstützt in Chromium ab Version 108, Firefox ab etwa 130 und Safari ab 18. Bei älteren Browsern prüfst du die Unterstützung vorher, z. B. mit
CSS.supports('content-visibility', 'auto')und'ContentVisibilityAutoStateChangeEvent' in window, und sorgst dafür, dass die Seite ohne das Ereignis weiter funktioniert (etwa mit einemIntersectionObserver). Die Eigenschaftoncontentvisibilityautostatechangegibt es nicht überall.addEventListenerist der sichere Weg.