Responsive Images in CSS

Avatar of Chris Coyier
Chris Coyier am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Der Begriff „responsive images“ steht mittlerweile für „responsive Bilder in HTML“, also die Attribute srcset und sizes für <img> sowie das <picture>-Element. Aber wie spiegeln sich die Möglichkeiten, die diese Dinge bieten, in CSS wider?

CSS war in den letzten Jahren generell kaum am Weg der responsiven Bilder beteiligt. Das hat gute Gründe: CSS verfügt bereits über die Werkzeuge. Responsive Images war in gewisser Weise nur ein Nachziehen dessen, was CSS bereits konnte. Schauen wir uns das mal an.

srcset in CSS

In HTML sieht srcset so aus (entnommen von der Picturefill-Website)

<img srcset="
  examples/images/image-384.jpg 1x, 
  examples/images/image-768.jpg 2x
" alt="…">

Ein Bild für 1x-Displays, ein größeres Bild für 2x-Displays. Wenn wir dasselbe tun wollten, nur als background-image in CSS, könnten wir das so machen:

.img {
  background-image: url(examples/images/image-384.jpg); 
}
@media 
  (-webkit-min-device-pixel-ratio: 2), 
  (min-resolution: 192dpi) { 
  .img {
    background-image: url(examples/images/image-768.jpg);
  }
}

Es gibt hier jedoch einen Unterschied. Soweit ich das verstehe, ist die Art und Weise, wie srcset spezifiziert ist, eher ein Vorschlag. Das Attribut und der Wert liefern Informationen darüber, was verfügbar ist, und der Browser entscheidet, was im Moment am besten ist. Oder zumindest *könnte* er das, wenn Browser es so implementieren würden. Mit einer @media-Abfrage wird das deklarierte auch so umgesetzt.

Auflösungsmedienabfragen werden recht gut unterstützt

Browserunterstützung für Auflösungsmedienabfragen

Es gibt auch noch eine andere Methode, die tatsächlich näher daran ist, wie srcset funktioniert, und das ist die Verwendung der image-set()-Funktion in CSS.

.img {
  background-image: url(examples/images/image-384.jpg);
  background-image: 
    -webkit-image-set(
      url(examples/images/image-384.jpg) 1x,
      url(examples/images/image-768.jpg) 2x,
    );
  background-image: 
    image-set(
      url(examples/images/image-384.jpg) 1x,
      url(examples/images/image-768.jpg) 2x,
    );
}

Sie hat etwas weniger Unterstützung als Auflösungsabfragen.

Browserunterstützung für image-set()

Sie kommt srcset viel näher, nicht nur weil die Syntax ähnlich ist, sondern weil sie dem Browser eine Mitsprache erlaubt. Laut der (noch im Entwurf befindlichen) Spezifikation

Die image-set()-Funktion ermöglicht es einem Autor, die meisten dieser Probleme zu ignorieren, indem einfach mehrere Auflösungen eines Bildes bereitgestellt werden und der UA entscheidet, welche in einer bestimmten Situation am besten geeignet ist.

Es gibt keinen perfekten 1:1 Ersatz für srcset in CSS, aber das hier kommt dem schon ziemlich nahe.

sizes in CSS

Das sizes-Attribut in HTML hat eine *sehr direkte* Beziehung zu CSS. Tatsächlich besagt es im Grunde: *„So beabsichtige ich, dieses Bild in CSS zu skalieren, und ich lasse dich das jetzt wissen, weil du diese Information vielleicht genau jetzt brauchst und nicht warten kannst, bis CSS heruntergeladen ist.“*

Beispiel

<img
  sizes="(min-width: 40em) 80vw, 100vw"
  srcset=" ... "
  alt="…">

Dies setzt etwas wie das hier im CSS voraus:

img {
  width: 100%;
}
@media (min-width: 40em) {
  /* Probably some parent element that limits the img width */
  main {
    width: 80%;
  }
}

Aber sizes allein tut nichts. Man kombiniert es mit srcset, das bekannte Breiten liefert, damit der Browser eine Wahl treffen kann. Nehmen wir mal nur ein Paar Bilder wie:

<img
  sizes="(min-width: 400px) 80vw, 100vw"
  srcset="examples/images/small.jpg 375w,
          examples/images/big.jpg 1500w"
  alt="…">

Die Informationen im obigen Markup geben dem Browser, was er braucht, um das beste Bild für ihn zu ermitteln. Der Browser kennt 1) seine eigene Viewport-Größe und 2) seine eigene Pixeldichte.

Vielleicht ist der Browser-Viewport 320 Pixel breit und es ist ein 1x-Display. Er weiß nun auch, dass er dieses Bild mit 100vw anzeigen wird. Also muss er zwischen den beiden bereitgestellten Bildern wählen. Er macht ein paar Berechnungen.

375 (Größe von Bild Nr. 1) / 320 (verfügbare Pixel zur Anzeige des Bildes) = 1,17
1500 (Größe von Bild Nr. 2) / 320 (verfügbare Pixel zur Anzeige des Bildes) = 4,69

1,17 ist näher an 1 (es ist ein 1x-Display), daher gewinnt das 375w-Bild. Es wird versuchen, nicht darunter zu gehen, also würde 1,3 besser sein als 0,99, soweit ich das verstehe.

Nehmen wir an, es war ein 2x-Display. Das verdoppelt die benötigten Pixel zur Anzeige der Bilder, also ist die Berechnung:

375 / 640 = 0.59
1500 / 640 = 2.34

Hier gewinnt 2,34, und es wird das 1500w-Bild angezeigt. Wie wäre es mit einem 1x-Display mit einem 1200px-Viewport?

375 / (80% von 1200) = 0,39
1500 / (80% von 1200) = 1,56

Hier gewinnt das 1500w-Bild.

Das ist irgendwie seltsam und knifflig, in CSS zu schreiben. Wenn wir *nur* an 1x-Displays denken, landen wir bei Logik wie…

  • Wenn der Viewport kleiner als 375px ist, verwenden Sie das 375w-Bild.
  • Wenn der Viewport größer als 375px, aber kleiner als 400px ist, verwenden Sie das 1500w-Bild (da wir sonst hochskalieren würden).
  • Bei 400px wird das Bild 80vw breit, daher ist es sicher, das 375w-Bild für ein kleines bisschen zu verwenden (zwischen 400px und 468px).
  • Über 468px hinaus verwenden Sie das 1500w-Bild.

Das könnten wir so schreiben:

img {
  background-image: url(small.jpg);
}
/* Only override this if one of the conditions for the 1500w image is met */
@media 
  (min-width: 375px) and (max-width: 400px),
  (min-width: 468px) {
  main {
    background-image: url(large.jpg);
  }
}

In diesem speziellen Fall, einem 2x-Display, werden selbst bei einer wirklich schmalen Breite von 300px immer noch 600px für eine Mindestqualität von 1,0 benötigt, daher würden wir das auch zur Logik hinzufügen:

.img {
  background-image: url(small.jpg);
}
/* Only override this if one of the conditions for the 1500w image is met */
@media 
  (min-width: 375px) and (max-width: 400px),
  (min-width: 468px),
  (-webkit-min-device-pixel-ratio: 2), 
  (min-resolution: 192dpi) {
  .img {
    background-image: url(large.jpg);
  }
}

Die Komplexität steigt exponentiell mit der Anzahl der Breakpoints (Größen) und der Anzahl der bereitgestellten Bilder. Und es ist immer noch kein perfekter Ersatz für das, was responsive Bilder (in HTML) können, da es dem Browser keine Diskretion überlässt (z. B. die Möglichkeit für einen Browser, andere Faktoren [d. h. Bandbreite] zu berücksichtigen, um ein Bild auszuwählen).

picture in CSS

Ein Beispiel

<picture>
  <source srcset="extralarge.jpg" media="(min-width: 1000px)">
  <source srcset="large.jpg" media="(min-width: 800px)">
  <img srcset="medium.jpg" alt="…">
</picture>

Diese Art von Sache ist eine ziemlich direkte Umwandlung in Medienabfragen. Die genauen Medienabfragen sind zum Kopieren da.

.img {
  background-image: url(medium.jpg);
}
@media (min-width: 800px) {
  .img {
    background-image: url(large.jpg);
  }
}
@media (min-width: 1000px) {
  .img {
    background-image: url(extralarge.jpg);
  }
}

Keine Überraschung, das kann komplizierter werden, denn srcset kann auch innerhalb des picture-Elements seine Wirkung entfalten.

<picture>
  <source srcset="large.jpg, extralarge.jpg 2x" media="(min-width: 800px)">
  <img srcset="small.jpg, medium.jpg 2x" alt="…">
</picture>

Was sich übersetzt zu:

.img {
  background-image: url(small.jpg);
}
@media
  (-webkit-min-device-pixel-ratio: 2), 
  (min-resolution: 192dpi) {
  .img {
    background-image: url(medium.jpg);
  }
}
@media
  (min-width: 800px) {
  .img {
    background-image: url(large.jpg);
  }
}
@media
  (-webkit-min-device-pixel-ratio: 2) and (min-width: 800px), 
  (min-resolution: 192dpi) and (min-width: 800px) {
  .img {
    background-image: url(extralarge.jpg);
  }
}

Auch hier ist es nur eine Frage der Zeit, bis die Komplexität explodiert, wenn man ein paar weitere Bilder und Bedingungen hinzufügt. Mit image-set() etwas besser.

.img {
  background-image: url(small.jpg);
  background-image: 
    -webkit-image-set(
      "small.jpg" 1x,
      "medium.jpg" 2x,
    );
  background-image: 
    image-set(
      "small.jpg" 1x,
      "medium.jpg" 2x,
    );
}
@media
  (min-width: 800px) {
  .img {
    background-image: url(large.jpg);
    background-image: 
      -webkit-image-set(
        "large.jpg" 1x,
        "extralarge.jpg" 2x,
      );
    background-image: 
      image-set(
        "large.jpg" 1x,
        "extralarge.jpg" 2x,
      );
  }
}

Könnten Mixins helfen?

Wahrscheinlich? Ich würde gerne ein Sass @mixin sehen, das all diese Parameter aufnimmt, die Bildgrößen berücksichtigt (die es selbst durch Zugriffe auf die Festplatte ermittelt) und daraus qualitativ hochwertigen Responsive-Images-in-CSS-Code generiert. Vielleicht gibt es sogar eine Möglichkeit, Auflösungsabfragen und image-set()-Syntaxen zu kombinieren?

Tun wir das wirklich?

Ich bin neugierig, wie viele Leute aktiv responsive Bilder in CSS behandeln. Vielleicht sogar auf eine Art Mittelweg, indem sie nur größere Bilder bei größeren Breakpoints austauschen? Ich frage mich, ob die Akzeptanz von responsive Bildern in CSS geringer ist als die von Picture/srcset, da letzteres oft automatisiert ist?