Zum Inhalt springen

Responsive Bilder: srcset, sizes und moderne Formate

Meistere die Auslieferung responsiver Bilder mit srcset, sizes, dem picture-Element und Formaten wie AVIF und WebP — schneller, ohne Qualitätsverlust.

5 Min. Lesezeit
Nebeneinander-Vergleich, der dasselbe Hero-Bild in unterschiedlichen Auflösungen und Formaten für Mobilgeräte, Tablets und Desktop-Viewports zeigt

Bilder machen auf den meisten Webseiten den Großteil der übertragenen Bytes aus. Ein 2400px großes Hero-Bild an einen 375px breiten mobilen Bildschirm auszuliefern verschwendet Bandbreite, drückt die Performance-Werte nach unten und frustriert Nutzer mit langsameren Verbindungen. Trotzdem liefern die meisten Entwickler weiterhin nur eine einzige Bildgröße aus und betrachten die Aufgabe als erledigt.

Responsive Bilder lösen dieses Problem, indem sie den Browser anhand von Viewport-Breite, Pixeldichte und unterstützten Formaten die optimale Bildvariante auswählen lassen. HTML bringt die nötigen Werkzeuge bereits mit—srcset, sizes und das <picture>-Element. Die Herausforderung besteht darin, zu verstehen, wie sie zusammenspielen.

Das Problem mit Bildern aus einer einzigen Quelle

Ein einzelnes <img>-Tag zwingt jedes Gerät dazu, dieselbe Datei herunterzuladen, unabhängig von Bildschirmgröße oder Leistungsfähigkeit.

htmlhtml
<!-- ❌ One size for all devices -->
<img
  src="/images/hero-2400.jpg"
  alt="Mountain landscape at sunrise"
  width="2400"
  height="1600"
/>
<!--
  Mobile user on 3G: downloads 800KB for a 375px screen
  Desktop user on fiber: gets the right size by accident
  Neither gets modern formats like AVIF or WebP
-->
htmlhtml
<!-- ✅ Responsive with srcset and sizes -->
<img
  src="/images/hero-800.jpg"
  srcset="
    /images/hero-400.jpg   400w,
    /images/hero-800.jpg   800w,
    /images/hero-1200.jpg 1200w,
    /images/hero-1600.jpg 1600w,
    /images/hero-2400.jpg 2400w
  "
  sizes="
    (max-width: 640px) 100vw,
    (max-width: 1024px) 80vw,
    1200px
  "
  alt="Mountain landscape at sunrise"
  width="2400"
  height="1600"
  loading="lazy"
  decoding="async"
/>

Das Attribut srcset teilt dem Browser mit, welche Bildbreiten verfügbar sind. Das Attribut sizes teilt dem Browser mit, wie breit das Bild bei jedem Breakpoint dargestellt wird. Der Browser kombiniert diese Informationen mit dem Device Pixel Ratio, um die optimale Datei auszuwählen.

srcset und sizes verstehen

Der w-Deskriptor in srcset gibt die intrinsische Breite jeder Bilddatei an. Der Browser nutzt sizes, um die dargestellte Breite zu bestimmen, und wählt dann die beste Übereinstimmung.

htmlhtml
<!--
  How the browser decides:
 
  Device: iPhone 14 (390px viewport, 3x DPR)
  sizes says: at max-width 640px → 100vw → 390px
  Effective pixels needed: 390 × 3 = 1170px
  Browser picks: hero-1200.jpg (closest match ≥ 1170)
 
  Device: MacBook Pro (1440px viewport, 2x DPR)
  sizes says: at max-width 1024px? No.
  Falls through to default: 1200px
  Effective pixels needed: 1200 × 2 = 2400px
  Browser picks: hero-2400.jpg
-->
 
<img
  src="/images/hero-800.jpg"
  srcset="
    /images/hero-400.jpg   400w,
    /images/hero-800.jpg   800w,
    /images/hero-1200.jpg 1200w,
    /images/hero-1600.jpg 1600w,
    /images/hero-2400.jpg 2400w
  "
  sizes="
    (max-width: 640px) 100vw,
    (max-width: 1024px) 80vw,
    1200px
  "
  alt="Dashboard analytics view"
  width="2400"
  height="1600"
/>

Ein häufiger Fehler: sizes immer auf 100vw zu setzen. Auf dem Desktop erreichen Bilder innerhalb eines Containers nur selten die volle Viewport-Breite. Ungenaue sizes-Werte führen dazu, dass der Browser zu große Bilder herunterlädt.

htmlhtml
<!-- ❌ sizes always 100vw wastes bandwidth on desktop -->
<img
  srcset="
    /images/card-400.jpg 400w,
    /images/card-800.jpg 800w
  "
  sizes="100vw"
  alt="Product card"
/>
 
<!-- ✅ Accurate sizes match actual layout -->
<img
  srcset="
    /images/card-400.jpg 400w,
    /images/card-800.jpg 800w
  "
  sizes="
    (max-width: 640px) 100vw,
    (max-width: 1024px) 50vw,
    33vw
  "
  alt="Product card"
/>

Das picture-Element für die Formataushandlung

Während srcset das Umschalten der Auflösung übernimmt, kümmert sich <picture> um die Formataushandlung und die Art Direction. Der Browser wählt das erste <source>-Element, das er unterstützt.

htmlhtml
<picture>
  <!-- AVIF: smallest file, newest format -->
  <source
    type="image/avif"
    srcset="
      /images/hero-400.avif   400w,
      /images/hero-800.avif   800w,
      /images/hero-1200.avif 1200w,
      /images/hero-1600.avif 1600w
    "
    sizes="(max-width: 640px) 100vw, 1200px"
  />
  <!-- WebP: good compression, wide support -->
  <source
    type="image/webp"
    srcset="
      /images/hero-400.webp   400w,
      /images/hero-800.webp   800w,
      /images/hero-1200.webp 1200w,
      /images/hero-1600.webp 1600w
    "
    sizes="(max-width: 640px) 100vw, 1200px"
  />
  <!-- JPEG fallback: universal support -->
  <img
    src="/images/hero-800.jpg"
    srcset="
      /images/hero-400.jpg   400w,
      /images/hero-800.jpg   800w,
      /images/hero-1200.jpg 1200w,
      /images/hero-1600.jpg 1600w
    "
    sizes="(max-width: 640px) 100vw, 1200px"
    alt="Mountain landscape at sunrise"
    width="1600"
    height="1067"
    loading="lazy"
    decoding="async"
  />
</picture>

AVIF erzielt bei vergleichbarer visueller Qualität in der Regel 50% kleinere Dateien als JPEG. WebP liegt zwischen den beiden. Indem AVIF zuerst aufgeführt wird, erhalten Browser, die es unterstützen, die kleinsten Dateien, während ältere Browser auf WebP oder JPEG zurückfallen.

Art Direction mit Media Queries

Manchmal benötigen unterschiedliche Viewports grundlegend andere Bildausschnitte, nicht nur andere Auflösungen. Ein breites Panorama-Hero-Bild auf dem Desktop braucht auf dem Handy womöglich einen quadratischen Ausschnitt, damit das Motiv sichtbar bleibt.

htmlhtml
<picture>
  <!-- Mobile: square crop focused on subject -->
  <source
    media="(max-width: 640px)"
    srcset="
      /images/hero-mobile-400.avif  400w,
      /images/hero-mobile-800.avif  800w
    "
    sizes="100vw"
    type="image/avif"
  />
  <source
    media="(max-width: 640px)"
    srcset="
      /images/hero-mobile-400.webp  400w,
      /images/hero-mobile-800.webp  800w
    "
    sizes="100vw"
    type="image/webp"
  />
  <!-- Desktop: wide landscape -->
  <source
    srcset="
      /images/hero-desktop-1200.avif 1200w,
      /images/hero-desktop-1600.avif 1600w,
      /images/hero-desktop-2400.avif 2400w
    "
    sizes="100vw"
    type="image/avif"
  />
  <img
    src="/images/hero-desktop-1200.jpg"
    srcset="
      /images/hero-desktop-1200.jpg 1200w,
      /images/hero-desktop-1600.jpg 1600w,
      /images/hero-desktop-2400.jpg 2400w
    "
    sizes="100vw"
    alt="Team collaborating in an open office"
    width="2400"
    height="800"
  />
</picture>

Bildgenerierung mit Sharp automatisieren

Mehrere Größen und Formate manuell zu erstellen ist auf Dauer nicht praktikabel. Automatisieren Sie den Vorgang mit Sharp in einem Build-Skript.

tstypescript
import sharp from "sharp";
import { readdir, mkdir } from "node:fs/promises";
import { join, parse } from "node:path";
 
const INPUT_DIR = "images/originals";
const OUTPUT_DIR = "public/images";
const WIDTHS = [400, 800, 1200, 1600, 2400];
const FORMATS = ["avif", "webp", "jpg"] as const;
 
type ImageFormat = (typeof FORMATS)[number];
 
const FORMAT_OPTIONS: Record<ImageFormat, object> = {
  avif: { quality: 60, effort: 6 },
  webp: { quality: 75, effort: 5 },
  jpg: { quality: 80, mozjpeg: true },
};
 
async function processImage(
  inputPath: string
): Promise<void> {
  const { name } = parse(inputPath);
  const image = sharp(inputPath);
  const metadata = await image.metadata();
  const originalWidth = metadata.width ?? 2400;
 
  for (const format of FORMATS) {
    for (const width of WIDTHS) {
      if (width > originalWidth) continue;
 
      const outputPath = join(
        OUTPUT_DIR,
        `${name}-${width}.${format}`
      );
 
      await sharp(inputPath)
        .resize(width, null, {
          withoutEnlargement: true,
          fit: "inside",
        })
        .toFormat(format, FORMAT_OPTIONS[format])
        .toFile(outputPath);
 
      console.log(`  ✅ ${name}-${width}.${format}`);
    }
  }
}
 
async function main() {
  await mkdir(OUTPUT_DIR, { recursive: true });
 
  const files = await readdir(INPUT_DIR);
  const images = files.filter((f) =>
    /\.(jpg|jpeg|png|tiff)$/i.test(f)
  );
 
  console.log(
    `Processing ${images.length} images...\n`
  );
 
  for (const file of images) {
    console.log(`📸 ${file}`);
    await processImage(join(INPUT_DIR, file));
  }
 
  console.log("\n✅ All images processed");
}
 
main();

Umsetzung in React/Next.js

Moderne Frameworks abstrahieren responsive Bilder oft, aber das Verständnis des zugrunde liegenden HTML hilft, wenn diese Abstraktion an ihre Grenzen stößt.

tsxtsx
// ResponsiveImage.tsx
interface ImageProps {
  src: string; // base name without extension
  alt: string;
  width: number;
  height: number;
  sizes: string;
  priority?: boolean;
  className?: string;
}
 
const WIDTHS = [400, 800, 1200, 1600, 2400];
 
function buildSrcSet(
  base: string,
  format: string
): string {
  return WIDTHS.map(
    (w) => `/images/${base}-${w}.${format} ${w}w`
  ).join(", ");
}
 
export function ResponsiveImage({
  src,
  alt,
  width,
  height,
  sizes,
  priority = false,
  className,
}: ImageProps) {
  return (
    <picture>
      <source
        type="image/avif"
        srcSet={buildSrcSet(src, "avif")}
        sizes={sizes}
      />
      <source
        type="image/webp"
        srcSet={buildSrcSet(src, "webp")}
        sizes={sizes}
      />
      <img
        src={`/images/${src}-800.jpg`}
        srcSet={buildSrcSet(src, "jpg")}
        sizes={sizes}
        alt={alt}
        width={width}
        height={height}
        loading={priority ? "eager" : "lazy"}
        decoding={priority ? "sync" : "async"}
        fetchPriority={priority ? "high" : "auto"}
        className={className}
      />
    </picture>
  );
}
 
// Usage
<ResponsiveImage
  src="hero-landscape"
  alt="Mountain landscape at sunrise"
  width={2400}
  height={1600}
  sizes="(max-width: 640px) 100vw, 1200px"
  priority
/>

Das Wichtigste in Kürze

Verwenden Sie srcset mit Breiten-Deskriptoren (w), um mehrere Bildauflösungen bereitzustellen, und sizes, um dem Browser die dargestellte Breite bei jedem Breakpoint mitzuteilen—präzise sizes-Werte sind entscheidend, weil der Browser sie nutzt, um vor dem Layout zu berechnen, welches Bild heruntergeladen werden soll. Das <picture>-Element übernimmt die Formataushandlung, indem es Quellen in Prioritätsreihenfolge auflistet (zuerst AVIF, dann WebP, dann JPEG als Fallback), sodass jeder Browser das effizienteste von ihm unterstützte Format herunterladen kann. Art Direction erfordert separate <source>-Elemente mit media-Attributen, wenn unterschiedliche Viewports unterschiedliche Ausschnitte benötigen—die Auflösungsumschaltung mit srcset skaliert nur dasselbe Bild, sie verändert nicht die Bildkomposition. Automatisieren Sie die Bildgenerierung mit Werkzeugen wie Sharp, um während des Builds alle Größen- und Formatvarianten aus den Quelldateien zu erzeugen—das erspart die manuelle Bildbearbeitung und sorgt für ein konsistentes Ergebnis bei jedem Bild im Projekt.

Wilfredo Rujel

Wilfredo Rujel

Full-Stack-Softwareentwickler

Diesen Beitrag teilenX