Fairu

Responsive Images: Der komplette Guide zu srcset & sizes

15. Juni 2026 Fairu

Responsive Images ohne Handwedeln: wie srcset und sizes wirklich funktionieren, picture für Art Direction — und wie du die Varianten generierst, statt sie von Hand zu pflegen.

Responsive Images: Der komplette Guide zu srcset & sizes

Ein 2400px breites Hero-Bild sieht auf einem 27-Zoll-Monitor grandios aus. Schickst du dieselbe Datei an ein 375px breites iPhone, lädt der Browser sie trotzdem herunter, dekodiert sie und stellt sie mit etwa 13 % ihrer eigentlichen Auflösung dar – verschwendet werden die Bytes, die Bandbreite und die CPU-Zeit zum Dekodieren von Pixeln, die niemand je sieht. Ohne Responsive Images bleibt dir nur die Wahl, welche Nutzergruppe du benachteiligst: die große Datei ausliefern und Mobilgeräte bestrafen, oder die kleine Datei ausliefern und auf Desktop und Retina unscharf aussehen.

srcset und sizes lösen das, indem sie dem Browser mehrere Kandidaten übergeben und ihn wählen lassen. Dieser Guide erklärt genau, wie diese Auswahl funktioniert, wann du welchen Descriptor-Typ einsetzt, wann sich der Griff zu <picture> lohnt, welche Fehler es leise unbrauchbar machen und wie du all diese Varianten erzeugst, ohne dein Repo in eine Varianten-Farm zu verwandeln.

Das Mental Model: Du beschreibst, der Browser entscheidet

Die Grundidee hinter Responsive Images ist Arbeitsteilung. Du sagst dem Browser nicht, welche Datei er laden soll. Du gibst ihm die Fakten, die er braucht – welche Dateien es gibt, wie groß jede davon ist und wie breit das Bild im Layout dargestellt wird – und der Browser kombiniert das mit Informationen, die nur er zum Zeitpunkt der Anfrage hat (Viewport-Breite, Device Pixel Ratio, manchmal Netzwerkbedingungen), um zu entscheiden.

Genau der letzte Punkt ist der Grund, warum sich das nicht allein mit CSS oder Media Queries lösen lässt: Device Pixel Ratio und der tatsächliche Viewport sind Fakten zur Laufzeit. srcset und sizes machen diese Entscheidung schon auf HTML-Ebene berechenbar, noch bevor das Layout überhaupt existiert.

srcset mit w Descriptors: das Arbeitstier

Für Content-Bilder, die mit dem Layout mitskalieren, verwendest du Width Descriptors. Jeder Kandidat ist eine URL, gefolgt von der intrinsischen Breite der Datei in Pixeln:

1<img
2 src="photo-800.jpg"
3 srcset="
4 photo-400.jpg 400w,
5 photo-800.jpg 800w,
6 photo-1200.jpg 1200w,
7 photo-1600.jpg 1600w"
8 sizes="(max-width: 600px) 100vw, 800px"
9 width="800" height="450"
10 alt="...">

photo-800.jpg 800w bedeutet „diese Datei ist 800 Pixel breit". Beachte: Das w ist die tatsächliche Breite der Datei, kein Zielwert – du beschreibst dein Inventar, du stellst keine Anfrage.

Der Teil, den die meisten übersehen: mit w Descriptors bringt srcset ohne sizes gar nichts. Der Width Descriptor sagt dem Browser, wie groß jede Datei ist; sizes sagt ihm, wie groß der Slot sein wird. Er braucht beide Angaben, um zu rechnen.

Wie der Browser wirklich auswählt

Der Auswahlalgorithmus ist simpel, sobald man ihn einmal durchschaut hat:

1. Werte sizes relativ zum aktuellen Viewport aus, um die **Slot-Breite** in CSS-Pixeln zu bekommen. Im Beispiel oben passt ein 500px breites Phone auf (max-width: 600px) → Slot ist 100vw = 500px. Ein 1400px breiter Desktop fällt durch auf den Default-Wert → Slot ist 800px.

2. Multipliziere die Slot-Breite mit dem Device Pixel Ratio. Dieser 500px-Slot braucht auf einem 3x-Phone 500 × 3 = 1500px an tatsächlichen Bilddaten.

3. Wähle aus den srcset-Kandidaten die kleinste Datei, deren w ≥ der benötigten Breite ist. Brauchst du 1500px → gewinnt die 1600w-Datei. Ist keine groß genug, wird die größte verfügbare verwendet.

Das Elegante daran: Du musst DPR nie selbst behandeln. Du beschreibst die verfügbaren Breiten und den gerenderten Slot; die Pixeldichte berücksichtigt der Browser automatisch. Das ist auch der Grund, warum w Descriptors x Descriptors bei allem schlagen, was sich in der Größe ändert – sie passen sich an Viewport und Dichte an, nicht nur an die Dichte.

srcset mit x Descriptors: Bilder mit fixer Größe

Wenn ein Bild unabhängig vom Viewport in einer festen Größe dargestellt wird – ein Logo, ein Avatar, ein Icon – spielt die Viewport-Breite keine Rolle, nur die Pixeldichte zählt. Verwende Density Descriptors und lass sizes komplett weg:

1<img
2 src="logo-200.png"
3 srcset="logo-200.png 1x, logo-400.png 2x, logo-600.png 3x"
4 width="200" height="50"
5 alt="Company logo">

Der Browser wählt 2x auf einem Retina-Laptop, 3x auf einem hochauflösenden Phone, 1x auf einem Standardmonitor. Das ist einfacher als der wsizes-Tanz, aber nur korrekt, wenn sich die Anzeigegröße wirklich nie ändert. Für alles Content-Artige verwende w.

Eine praktische Feinheit: Gehst du in der Dichte hoch, kannst du bei der Qualität runtergehen. Ein 3x-Bild verträgt aggressivere Kompression als ein 1x-Bild, weil die zusätzlichen Pixel die Artefakte überdecken – so kann eine q=60 3x-Datei eine q=80 2x-Datei sowohl bei der Schärfe als auch bei den Bytes schlagen.

Wie du sizes richtig liest und schreibst

sizes ist eine Liste von (media condition) width-Paaren, die von links nach rechts ausgewertet werden, mit einem abschließenden nackten Wert als Default:

1sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px"

Lies das so: unter 600px füllt das Bild den Viewport; unter 1200px nimmt es die Hälfte; sonst sind es 800px. Die Breitenwerte können vw, px oder sogar calc() sein, um Padding und Gutter zu berücksichtigen – calc(100vw - 2rem) ist üblich für Full-Bleed-minus-Padding-Layouts.

Zwei Dinge, die du dir einbrennen solltest:

  • Die Media Conditions werden nicht von DPR beeinflusst – nur die Breitenwerte sind es. Der Browser multipliziert die aufgelöste Breite mit der Pixel Ratio, aber die Media-Query-Schwellenwerte vergleichen gegen den CSS-Viewport. Versuch nicht, für Retina zu "vormultiplizieren" – dann zählst du doppelt.

  • sizes beschreibt das Layout, muss also zum Layout passen. Wenn dein CSS das Bild auf dem Tablet mit 50vw rendert, sizes aber 100vw sagt, holt der Browser Bilder, die doppelt so groß sind wie nötig. Das ist mit Abstand der häufigste Responsive-Image-Bug.

Die Default-100vw-Falle

Wenn du sizes weglässt, aber w Descriptors verwendest, nimmt der Browser an, das Bild würde die volle Viewport-Breite einnehmen, und dimensioniert entsprechend groß – dadurch holt er sich bei jedem mehrspaltigen oder eingeschränkten Layout zu große Bilder. sizes weglassen heißt nicht "keine Meinung", sondern "die stärkste mögliche Meinung – und meist die falsche". Setz es immer.

Das <picture>-Element: Art Direction und Formate

srcsetsizes beantworten "gleiches Bild, welche Größe?" <picture> beantwortet zwei unterschiedliche Fragen.

Art Direction – wenn du pro Breakpoint einen unterschiedlichen Ausschnitt oder eine andere Bildkomposition willst, nicht nur eine andere Skalierung. Ein breites, cinematisches Hero-Bild am Desktop, ein enges Quadrat am Mobile:

1<picture>
2 <source media="(max-width: 600px)" srcset="hero-square-600.jpg">
3 <source media="(min-width: 601px)" srcset="hero-wide-1600.jpg">
4 <img src="hero-wide-1600.jpg" width="1600" height="600" alt="...">
5</picture>

Format-Verhandlung mit Fallback – biete moderne Formate an und lass den Browser das erste nehmen, das er unterstützt. Das <img> darin ist der zwingende Fallback und das Element, auf das du alt setzt:

1<picture>
2 <source type="image/avif" srcset="photo-800.avif 800w, photo-1600.avif 1600w" sizes="100vw">
3 <source type="image/webp" srcset="photo-800.webp 800w, photo-1600.webp 1600w" sizes="100vw">
4 <img src="photo-800.jpg" srcset="photo-1600.jpg 1600w" sizes="100vw"
5 width="1600" height="900" alt="...">
6</picture>

Faustregel: Wenn es das gleiche Bild in unterschiedlichen Größen ist, reicht ein einfaches <img srcset sizes> aus. Greif zu <picture> nur, wenn du unterschiedliches Bildmaterial pro Breakpoint oder explizite Formatkontrolle mit Fallbacks brauchst.

LCP und CLS nicht vergessen

Responsive Images sitzen direkt auf zwei Core Web Vitals, deshalb erfüllen ein paar Attribute doppelte Funktion:

  • Setz immer width und height (oder ein CSS-aspect-ratio). Damit kann der Browser den Slot reservieren, bevor die Bytes ankommen – das verhindert den Layout-Shift, der CLS zerschießt. Schau dir an: jedes Beispiel oben hat sie.

  • fetchpriority="high" auf deinem LCP-Bild (meist das Hero-Bild) sagt dem Browser, es zu priorisieren. Google Flights hat damit die LCP um rund 700ms verkürzt – mit genau diesem einen Attribut. Es hilft aber nur, wenn die Bytes dahinter schon klein sind.

  • loading="lazy" auf below-the-fold-Bildern verzögert deren Laden, damit sie nicht mit dem Hero-Bild konkurrieren. Lazy-load niemals das LCP-Bild selbst; das verzögert genau das, worauf du gemessen wirst.

Breakpoints wählen (ohne zu übertreiben)

Du brauchst nicht für jedes Gerät eine eigene Variante. Ein praktisches Set aus 3–5 Breiten deckt fast alles ab: 400w, 800w, 1200w, 1600w, plus 2400w nur, wenn du wirklich Full-Bleed-Heroes in 4K-Klasse hast. Mehr Kandidaten bedeuten mehr Dateien zum Erzeugen, Speichern und Cachen – bei abnehmendem Wahrnehmungsgewinn. Und auf Phone-großen Screens ist der Unterschied zwischen 2x und 3x ohnehin kaum wahrnehmbar, deshalb ist es eine vernünftige Vorgabe, Content-Bilder bei 2x zu kappen und 3x für kritisches Branding aufzusparen.

Der ehrliche Haken: Jemand muss all diese Dateien erzeugen

Jede Technik oben setzt voraus, dass die Varianten existieren. Das Markup ist der einfache Teil. Die eigentliche Arbeit ist, 400/800/1200/1600-Versionen von jedem Bild zu erzeugen, in JPEG und WebP und AVIF, das bei jedem Upload eines Redakteurs neu laufen zu lassen, jede Ableitung zu speichern und das alles mit vernünftigem Caching auszuliefern. Machst du das zur Build-Zeit, besitzt du eine Encoding-Pipeline (sharp, libvips, eine Job-Queue) plus einen wachsenden Berg an abgeleiteten Dateien. Machst du es falsch, zeigt dein srcset auf Dateien, die nicht existieren.

Das ist der Teil, den man auslagern sollte. Ein Transform-on-Request-Layer lässt dich eine einzige Quelldatei behalten und jede Variante als URL-Parameter ausdrücken, on demand erzeugt und am Edge gecacht. Mit https://fairu.app schrumpft das Markup von oben auf eine einzige Quelle plus Query-Strings – kein Build-Schritt, kein Wildwuchs an Ableitungen:

1<img
2 src="https://files.fairu.app/<asset-id>/file?width=800&format=webp"
3 srcset="
4 https://files.fairu.app/<asset-id>/file?width=400&format=webp 400w,
5 https://files.fairu.app/<asset-id>/file?width=800&format=webp 800w,
6 https://files.fairu.app/<asset-id>/file?width=1200&format=webp 1200w,
7 https://files.fairu.app/<asset-id>/file?width=1600&format=webp 1600w"
8 sizes="(max-width: 600px) 100vw, 800px"
9 width="800" height="450"
10 fetchpriority="high"
11 alt="...">

Jede Breite ist einfach ein Parameter; WebP ist der Standard, AVIF ist über format=avif verfügbar; und weil die Dateien on demand erzeugt und am Edge gecacht werden, pflegst du keine Varianten-Farm und keine Build-Pipeline. Du schreibst die srcsetsizes-Logik aus diesem Guide einmal, und die Bytes hinter jedem Kandidaten kümmern sich von selbst um sich.

Kurzübersicht

  • Content-Bild, das skaliert? <img srcset> mit w Descriptors und sizes. Niemals w ohne sizes.

  • Logo/Icon mit fester Größe? <img srcset> mit x Descriptors, kein sizes.

  • Unterschiedlicher Ausschnitt pro Breakpoint, oder Format-Fallbacks? <picture>.

  • Setz immer widthheight. LCP-Bild: fetchpriority="high", niemals lazy-loaded. Unterhalb des Folds: loading="lazy".

  • sizes muss zu deinem tatsächlichen CSS-Layout passen – dieser Mismatch ist Bug Nummer eins.

  • Halte Varianten auf 3–5 Breiten; begrenze Content-Bilder auf rund 2x.

Bekommst du das Markup richtig hin, macht der Browser den harten Teil für dich. Lass einen Transform-on-Request-Layer wie Fairu die Dateien übernehmen, und du musst nie wieder über die Variantenmatrix nachdenken. Starte eine kostenlose Testversion, um deine eigenen Bilder darauf zeigen zu lassen.

Mentioned in this post
Neugierig geworden?

Lust, gemeinsam zu kochen?

Ob Idee, Refactor oder Neubau — erzähl uns kurz von deinem Projekt. Wir melden uns innerhalb von 24 Stunden.