Zum Hauptinhalt springen
CSS-Optimierung Teil 1: Rendering & Performance
#CSS #Performance #Rendering #Browser

CSS-Optimierung Teil 1: Rendering & Performance


Flüssiges UI, wenig Reflows, gute FPS - Der komplette Guide zu CSS-Performance

7 Minuten Lesezeit

Dieser Teil behandelt alles rund um Rendering-Performance: Wie hältst du deine Webseite flüssig? Was triggert teure Reflows? Wie animierst du mit 60fps?

1. Browser Resize Performance

Das Problem: Ruckelnde Webseiten

Browser-Fenster resizen und die Seite ruckelt? 20fps statt 60fps? Das liegt meist am CSS.

Durch gezielte CSS-Optimierungen lassen sich 70-80% Performance-Gewinn erreichen.

Hauptübeltäter #1: Backdrop-Blur

/* ❌ Schlecht - GPU stirbt beim Resize */
.header {
  backdrop-filter: blur(10px);
}

Problem:

  • Browser muss Blur bei jeder Größenänderung neu berechnen
  • Full Repaint des gesamten Inhalts darunter
  • GPU-Auslastung: 80-90%

Lösung:

/* ✅ Gut - 60-80% weniger GPU-Last */
.header {
  background: rgba(255, 255, 255, 0.95);
}

Hauptübeltäter #2: Universelle Selektoren

/* ❌ Schlecht - Browser rechnet ALLES neu */
@media (max-width: 768px) {
  * {
    max-width: 100vw !important;
    overflow-x: hidden !important;
  }
}

Bei 500+ Elementen = massive Style Recalculations.

Lösung:

/* ✅ Gut - Nur betroffene Elemente */
@media (max-width: 768px) {
  .prose > *,
  .prose img,
  .prose pre,
  .prose table {
    max-width: 100%;
    overflow-x: auto;
  }
}

Einsparung: ~90% weniger Recalculations

Hauptübeltäter #3: Fehlende CSS Containment

/* ❌ Schlecht - Keine Isolation */
.container {
  position: relative;
}

/* ✅ Gut - Isoliert Layout-Änderungen */
.container {
  contain: layout style;
}

.card {
  contain: layout paint;
}

Was macht contain?

  • layout - Verhindert Layout-Propagation nach außen
  • paint - Begrenzt Repaints auf das Element
  • style - Isoliert CSS-Counter etc.
  • strict - Alle kombiniert

Einsparung: ~60% weniger Repaint-Zeit

Performance messen

// Chrome DevTools Console
performance.mark('resize-start');
window.addEventListener('resize', () => {
  performance.mark('resize-end');
  performance.measure('resize', 'resize-start', 'resize-end');
  console.log(performance.getEntriesByName('resize'));
});

Gute Werte:

  • Layout: < 5ms
  • Paint: < 10ms
  • Composite: < 2ms

2. Reflow & Repaint minimieren

Die Kosten-Hierarchie

Composite (günstig)     → ~2ms

Repaint (mittel)        → ~10ms

Reflow (teuer)          → ~20ms+

Properties und ihre Trigger

Nur Composite (billig!):

/* ✅ Günstig - nur GPU-Compositing */
.element {
  transform: translateX(100px);
  opacity: 0.5;
}

Repaint (mittel):

/* ⚠️ Mittel - kein Layout, aber neu zeichnen */
.element {
  color: red;
  background: blue;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

Reflow (teuer!):

/* ❌ Teuer - kompletter Layout-Durchlauf */
.element {
  width: 300px;
  height: 200px;
  margin: 20px;
  top: 50px;
}

Anti-Pattern: Layout-Thrashing

// ❌ Horror - 200 Reflows!
elements.forEach(el => {
  const height = el.offsetHeight; // LESEN → Reflow
  el.style.height = height + 10 + 'px'; // SCHREIBEN
});

// ✅ Gut - 1 Reflow
const heights = elements.map(el => el.offsetHeight);
elements.forEach((el, i) => {
  el.style.height = heights[i] + 10 + 'px';
});

Nested Flex/Grid vermeiden

/* ❌ Teuer - mehrfache Layout-Berechnungen */
.container {
  display: flex;
}
.item {
  flex: 1;
  display: flex;
}
.nested {
  flex: 1;
  display: grid;
}

/* ✅ Besser - flachere Hierarchie */
.container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}

Faustregel: Max. 3 Ebenen Flex/Grid-Verschachtelung.

Die 100vh-Falle

/* ❌ Mobile Safari hasst das */
.fullscreen {
  height: 100vh;
}

Problem: Address Bar ändert 100vh beim Scrollen → ständige Reflows.

Lösung:

:root {
  --vh: 1vh;
}

.fullscreen {
  height: calc(var(--vh, 1vh) * 100);
}
function setVH() {
  const vh = window.innerHeight * 0.01;
  document.documentElement.style.setProperty('--vh', `${vh}px`);
}
setVH();
window.addEventListener('resize', setVH);

3. Animation Performance

Die goldene Regel

Animiere nur transform und opacity.

Browser Compositing verstehen

Browser arbeiten in Layers. Wenn du nur transform/opacity änderst, überspringt der Browser Layout und Paint.

/* ❌ Langsam - Layout + Paint + Composite */
@keyframes bad {
  from { left: 0; }
  to { left: 100px; }
}

/* ✅ Schnell - nur Composite */
@keyframes good {
  from { transform: translateX(0); }
  to { transform: translateX(100px); }
}

Messbar:

  • left: ~20ms pro Frame → 50fps max
  • transform: ~2ms pro Frame → 60fps easy

will-change: Der GPU-Turbo

/* GPU-Layer erzwingen */
.animated {
  will-change: transform;
}

Wichtig: Dynamisch setzen, dann wieder entfernen!

element.addEventListener('mouseenter', () => {
  element.style.willChange = 'transform';
});

element.addEventListener('animationend', () => {
  element.style.willChange = 'auto'; // Wichtig!
});

Warum? Jeder Layer kostet RAM. will-change auf 1000 Elementen = mehrere MB verschwendet.

Long-running Animations

/* ❌ Läuft ewig, frisst Ressourcen */
.spinner {
  animation: spin 1s linear infinite;
}

/* ✅ Pause bei Invisibility */
.spinner {
  animation: spin 1s linear infinite;
  animation-play-state: running;
}

.spinner[hidden] {
  animation-play-state: paused;
}

Mit Intersection Observer:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    entry.target.style.animationPlayState = 
      entry.isIntersecting ? 'running' : 'paused';
  });
});

prefers-reduced-motion

/* Standard */
.element {
  animation: slide 0.3s ease-out;
}

/* Reduced Motion */
@media (prefers-reduced-motion: reduce) {
  .element {
    animation: none;
    transition: none;
  }
}

Barrierefreiheit-Regel: Kritische Infos nie nur animiert zeigen.


4. Scroll-Performance

position: sticky smart einsetzen

/* ✅ GPU-beschleunigt */
.header {
  position: sticky;
  top: 0;
  will-change: transform;
  contain: layout paint;
}

Problem: Ohne contain muss Browser bei jedem Scroll Layout neu berechnen.

overflow: auto vs. clip

/* ❌ Langsamer - Scrollbar-Layout */
.container {
  overflow-x: hidden;
}

/* ✅ Schneller - clippt ohne Scrollbar */
.container {
  overflow-x: clip;
}

Virtuelle Listen (Konzept)

Für sehr lange Listen (1000+ Items):

Idee: Rendere nur sichtbare Items + Puffer.

// Pseudo-Code
const visibleRange = calculateVisibleRange(scrollTop, itemHeight);
const itemsToRender = allItems.slice(
  visibleRange.start, 
  visibleRange.end
);

Libraries:

  • react-window
  • react-virtualized
  • @tanstack/virtual

Achtung: Nur bei wirklich langen Listen nötig (> 500 Items).

Passive Event Listeners

// ✅ Besseres Scrolling
element.addEventListener('scroll', handler, { passive: true });

passive: true sagt dem Browser: “Ich rufe nicht preventDefault() auf” → Browser kann sofort scrollen.


5. Bild & Media-Optimierung aus CSS-Sicht

object-fit

/* ❌ Verzerrt */
img {
  width: 100%;
  height: 300px;
}

/* ✅ Behält Seitenverhältnis */
img {
  width: 100%;
  height: 300px;
  object-fit: cover;
  object-position: center;
}

aspect-ratio

/* ✅ Verhindert Layout Shift */
.video-container {
  aspect-ratio: 16 / 9;
}

.avatar {
  aspect-ratio: 1;
  object-fit: cover;
}

Vor aspect-ratio:

/* ❌ Padding-Hack */
.video-container {
  padding-top: 56.25%; /* 16:9 */
  position: relative;
}

.video-container iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

image-set für Retina

.logo {
  background-image: image-set(
    url('logo.png') 1x,
    url('[email protected]') 2x,
    url('[email protected]') 3x
  );
}

Responsive Images (CSS-Dimension)

<!-- Modernes srcset -->
<img 
  src="image-800.jpg"
  srcset="
    image-400.jpg 400w,
    image-800.jpg 800w,
    image-1200.jpg 1200w
  "
  sizes="(max-width: 768px) 100vw, 800px"
  alt="Beschreibung"
/>

CSS unterstützt mit:

img {
  max-width: 100%;
  height: auto;
  display: block;
}

Loading-States

/* Skeleton während Laden */
.image-container {
  background: linear-gradient(
    90deg, 
    #f0f0f0 25%, 
    #e0e0e0 50%, 
    #f0f0f0 75%
  );
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
}

@keyframes loading {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.image-container img {
  opacity: 0;
  transition: opacity 0.3s;
}

.image-container img.loaded {
  opacity: 1;
}

KI-Audit: Rendering-Performance prüfen

Kopiere diesen Prompt in Claude, ChatGPT oder deine bevorzugte KI:

Analysiere mein Projekt auf CSS Rendering-Performance Probleme:

1. **Backdrop-Filter**: Finde alle `backdrop-filter` Nutzungen. Schlage Alternativen mit rgba() vor.

2. **Universelle Selektoren**: Suche nach `* { ... }` Selektoren, besonders mit `!important`. Zeige spezifischere Alternativen.

3. **CSS Containment**: Prüfe große Container (.container, .card, article, section) - fehlt `contain: layout` oder `contain: layout paint`?

4. **Teure Animationen**: Finde Animationen die width, height, top, left, margin nutzen. Schlage `transform` Alternativen vor.

5. **will-change**: Wird `will-change` statisch gesetzt statt dynamisch? Zeige wie man es per JS toggled.

6. **100vh Mobile**: Suche nach `height: 100vh` ohne CSS Custom Property Fallback.

7. **overflow: hidden vs clip**: Finde `overflow-x: hidden` und schlage `overflow-x: clip` vor wo sinnvoll.

Gib mir eine priorisierte Liste mit Code-Beispielen und Performance-Impact (hoch/mittel/niedrig).

Links: