Feature Detection, Conditionals und Gruppen mit Selektoren verwenden

Avatar of Jirka Vebr
Jirka Vebr am

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

CSS ist so konzipiert, dass neue Features relativ nahtlos hinzugefügt werden können. Seit Anbeginn der Sprache verlangen Spezifikationen von Browsern, dass sie alle Eigenschaften, Werte, Selektoren oder At-Rules, die sie nicht unterstützen, problemlos ignorieren. Folglich ist es in den meisten Fällen möglich, eine neuere Technologie erfolgreich einzusetzen, ohne Probleme in älteren Browsern zu verursachen.

Betrachten Sie die relativ neue Eigenschaft caret-color (sie ändert die Farbe des Cursors in Eingabefeldern). Ihre Unterstützung ist noch gering, aber das bedeutet nicht, dass wir sie heute nicht verwenden sollten.

.myInput {
  color: blue;
  caret-color: red;
}

Beachten Sie, wie wir sie direkt neben color platziert haben, eine Eigenschaft mit praktisch universeller Browserunterstützung; eine, die überall angewendet wird. In diesem Fall haben wir nicht explizit zwischen modernen und älteren Browsern unterschieden. Stattdessen verlassen wir uns einfach darauf, dass ältere Browser Funktionen ignorieren, die sie nicht unterstützen.

Es stellt sich heraus, dass dieses Muster in den allermeisten Situationen leistungsfähig genug ist.

Wenn Feature Detection notwendig ist

In einigen Fällen möchten wir jedoch wirklich eine moderne Eigenschaft oder einen modernen Wert verwenden, deren Nutzung sich erheblich von ihrem Fallback unterscheidet. In diesen Fällen ist @supports die Rettung.

@supports ist eine spezielle At-Rule, die es uns ermöglicht, bedingt beliebige Stile in Browsern anzuwenden, die eine bestimmte Eigenschaft und ihren Wert unterstützen.

@supports (display: grid) {
  /* Styles for browsers that support grid layout... */
}

Sie funktioniert analog zu @media-Abfragen, die Stile auch nur bedingt anwenden, wenn ein bestimmtes Prädikat erfüllt ist.

Um die Verwendung von @supports zu veranschaulichen, betrachten wir das folgende Beispiel: Wir möchten einen vom Benutzer hochgeladenen Avatar in einem schönen Kreis anzeigen, können aber nicht garantieren, dass die eigentliche Datei quadratische Abmessungen hat. Dafür wäre die Eigenschaft object-fit äußerst hilfreich; sie wird jedoch nicht von Internet Explorer (IE) unterstützt. Was tun wir dann?

Beginnen wir mit dem Markup

<div class="avatar">
  <img class="avatar-image" src="..." alt="..." />
</div>

Als nicht ganz so schöner Fallback werden wir die Bildbreite im Avatar quetschen, auf Kosten dessen, dass breitere Dateien den Avatarbereich nicht vollständig abdecken. Stattdessen wird unser einfarbiger Hintergrund darunter erscheinen.

.avatar {
  position: relative;
  width: 5em;
  height: 5em;
  border-radius: 50%;
  overflow: hidden;
  background: #cccccc; /* Fallback color */
}

.avatar-image {
  position: absolute;
  top: 50%;
  right: 0;
  bottom: 0;
  left: 50%;
  transform: translate(-50%, -50%);
  max-width: 100%;
}

Sie können dieses Verhalten hier in Aktion sehen

Siehe den Pen Demo-Fallback für object-fit von Jirka Vebr (@JirkaVebr) auf CodePen.

Beachten Sie, dass es ein quadratisches Bild, ein breites und ein hohes gibt.

Wenn wir nun object-fit verwenden, können wir den Browser entscheiden lassen, wie das Bild am besten positioniert wird, nämlich ob die Breite, Höhe oder beides gedehnt wird.

@supports (object-fit: cover) {
  .avatar-image {
    /* We no longer need absolute positioning or any transforms */
    position: static;
    transform: none;
    object-fit: cover;
    width: 100%;
    height: 100%;
  }
}

Das Ergebnis, für die gleichen Bildabmessungen, funktioniert gut in modernen Browsern

Siehe den Pen @supports object-fit Demo von Jirka Vebr (@JirkaVebr) auf CodePen.

Bedingte Selektorunterstützung

Obwohl die Selectors Level 4-Spezifikation noch ein Working Draft ist, werden einige der von ihr definierten Selektoren – wie :placeholder-shownbereits von vielen Browsern unterstützt. Wenn sich dieser Trend fortsetzt (und der Entwurf die meisten seiner aktuellen Vorschläge beibehält), wird diese Ebene der Spezifikation mehr neue Selektoren einführen als jeder seiner Vorgänger. In der Zwischenzeit, und auch solange IE noch existiert, werden CSS-Entwickler ein noch vielfältigeres und instabileres Spektrum von Browsern mit neu entstehender Unterstützung für diese Selektoren ansprechen müssen.

Es wird sehr nützlich sein, Feature Detection für Selektoren durchzuführen. Leider ist @supports nur zum Testen der Unterstützung von Eigenschaften und ihren Werten gedacht, und selbst der neueste Entwurf seiner Spezifikation scheint dies nicht zu ändern. Seit seiner Einführung hat es jedoch eine spezielle Produktionsregel in seiner Grammatik definiert, deren einziger Zweck es ist, Raum für mögliche abwärtskompatible Erweiterungen zu schaffen, und so ist es durchaus denkbar, dass eine zukünftige Version die Möglichkeit hinzufügt, die Unterstützung für bestimmte Selektoren zu bedingen. Dennoch bleibt diese Eventualität rein hypothetisch.

Selektor-Gegenstück zu @supports

Zunächst ist es wichtig zu betonen, dass, analog zum oben genannten caret-color-Beispiel, wo @supports wahrscheinlich nicht notwendig ist, viele Selektoren auch nicht explizit getestet werden müssen. Beispielsweise könnten wir einfach ::selection abgleichen und uns keine Sorgen über Browser machen, die es nicht unterstützen, da es nicht das Ende der Welt wäre, wenn das Erscheinungsbild der Auswahl dem Standard des Browsers entspricht.

Dennoch gibt es Fälle, in denen eine explizite Feature-Detection für Selektoren sehr wünschenswert wäre. Im Rest dieses Artikels werden wir ein Muster zur Adressierung solcher Bedürfnisse vorstellen und es anschließend mit :placeholder-shown verwenden, um eine reine CSS-Alternative zu den Material Design Textfeldern mit schwebendem Label zu erstellen.

Grundlegende Eigenschaftsgruppen von Selektoren

Um Duplikate zu vermeiden, ist es möglich, mehrere identische Deklarationen zu einer kommagetrennten Liste von Selektoren zu verdichten, was als Gruppierung von Selektoren bezeichnet wird.

So können wir verwandeln

.foo { color: red }
.bar { color: red }

…in

.foo, .bar { color: red }

Wie die Selectors Level 3-Spezifikation jedoch warnt, sind diese nur gleichwertig, da alle beteiligten Selektoren gültig sind. Gemäß der Spezifikation, wenn einer der Selektoren in der Gruppe ungültig ist, wird die gesamte Gruppe ignoriert. Folglich sind die Selektoren

..foo { color: red } /* Note the extra dot */
.bar { color: red }

…könnten nicht sicher gruppiert werden, da der erste Selektor ungültig ist. Wenn wir sie gruppieren würden, würden wir den Browser dazu bringen, die Deklaration für den letzteren ebenfalls zu ignorieren.

Es ist erwähnenswert, dass es für einen Browser keinen Unterschied zwischen einem ungültigen Selektor und einem Selektor gibt, der nur gemäß einer neueren Version der Spezifikation gültig ist oder den der Browser nicht kennt. Für den Browser sind beide einfach ungültig.

Wir können diese Eigenschaft nutzen, um die Unterstützung für einen bestimmten Selektor zu testen. Alles, was wir brauchen, ist ein Selektor, von dem wir garantieren können, dass er nichts abgleicht. In unseren Beispielen verwenden wir :not(*).

.foo { color: red }

:not(*):placeholder-shown,
.foo {
  color: green
}

Lassen Sie uns aufschlüsseln, was hier passiert. Ein älterer Browser wendet die erste Regel erfolgreich an, aber wenn er die restlichen verarbeitet, findet er den ersten Selektor in der Gruppe ungültig, da er :placeholder-shown nicht kennt, und ignoriert daher die gesamte Selektorgruppe. Folglich bleiben alle Elemente, die .foo abgleichen, rot. Im Gegensatz dazu wird ein neuerer Browser wahrscheinlich mit den Augen rollen, wenn er auf :not(*) stößt (was nie etwas abgleicht), aber er wird die gesamte Selektorgruppe nicht verwerfen. Stattdessen überschreibt er die vorherige Regel, und somit werden alle Elemente, die .foo abgleichen, grün.

Beachten Sie die Ähnlichkeit mit @supports (oder jeder @media-Abfrage, was die Sache betrifft) in Bezug auf die Verwendung. Wir geben zuerst den Fallback an und überschreiben ihn dann für Browser, die ein Prädikat erfüllen, was in diesem Fall die Unterstützung für einen bestimmten Selektor ist – wenn auch auf etwas umständliche Weise geschrieben.

Siehe den Pen @supports für Selektoren von Jirka Vebr (@JirkaVebr) auf CodePen.

Beispiel aus der Praxis

Wir können diese Technik für unser Eingabefeld mit schwebendem Label verwenden, um Browser, die :placeholder-shown unterstützen, von denen zu trennen, die es nicht tun – eine Pseudoklasse, die für dieses Beispiel absolut entscheidend ist. Der Einfachheit halber, trotz bester UI-Praktiken, werden wir unseren Fallback nur auf den tatsächlichen Platzhalter beschränken.

Beginnen wir mit dem Markup

<div class="input">
  <input class="input-control" type="email" name="email" placeholder="Email" id="email" required />
  <label class="input-label" for="email">Email</label>
</div>

Wie zuvor ist der Schlüssel, zuerst Stile für ältere Browser hinzuzufügen. Wir verstecken das Label und setzen die Farbe des Platzhalters.

.input {
  height: 3.2em;
  position: relative;
  display: flex;
  align-items: center;
  font-size: 1em;
}

.input-control {
  flex: 1;
  z-index: 2; /* So that it is always "above" the label */
  border: none;
  padding: 0 0 0 1em;
  background: transparent;
  position: relative;
}

.input-label {
  position: absolute;
  top: 50%;
  right: 0;
  bottom: 0;
  left: 1em; /* Align this with the control's padding */
  z-index: 1;
  display: none; /* Hide this for old browsers */
  transform-origin: top left;
  text-align: left;
}

Für moderne Browser können wir den Platzhalter effektiv deaktivieren, indem wir seine color auf transparent setzen. Wir können auch das input und das label relativ zueinander ausrichten, wenn der Platzhalter angezeigt wird. Zu diesem Zweck können wir auch den Geschwisterselektor verwenden, um das label im Verhältnis zum Zustand des input zu gestalten.

.input-control:placeholder-shown::placeholder {
  color: transparent;
}

.input-control:placeholder-shown ~ .input-label {
  transform: translateY(-50%)
}

.input-control:placeholder-shown {
  transform: translateY(0);
}

Schließlich der Trick! Genau wie oben überschreiben wir die Stile für das label und das input für moderne Browser und den Zustand, in dem der Platzhalter nicht angezeigt wird. Dies beinhaltet das Auslagern des label und seine Verkleinerung.

:not(*):placeholder-shown,
.input-label {
  display: block;
  transform: translateY(-70%) scale(.7);

}
:not(*):placeholder-shown,
.input-control {
  transform: translateY(35%);
}

Mit allen Teilen zusammen, sowie weiteren Stilen und Konfigurationsoptionen, die orthogonal zu diesem Beispiel sind, können Sie die vollständige Demo sehen

Siehe den Pen CSS-only @supports für Selektoren Demo von Jirka Vebr (@JirkaVebr) auf CodePen.

Zuverlässigkeit und Grenzen dieser Technik

Grundsätzlich erfordert diese Technik einen Selektor, der nichts abgleicht. Zu diesem Zweck haben wir :not(*) verwendet; dessen Unterstützung ist jedoch ebenfalls begrenzt. Der universelle Selektor * wird sogar von IE 7 unterstützt, während die :not-Pseudoklasse erst seit IE 9 implementiert ist, was somit der älteste Browser ist, in dem dieser Ansatz funktioniert. Ältere Browser würden unsere Selektorgruppen aus dem falschen Grund ablehnen – sie unterstützen :not nicht! Alternativ könnten wir einen Klassenselektor wie .foo oder einen Typselektor wie foo verwenden und somit selbst die ältesten Browser unterstützen. Dennoch machen diese den Code weniger lesbar, da sie nicht vermitteln, dass sie niemals etwas abgleichen sollen, und somit ist für die meisten modernen Websites :not(*) die beste Option.

Was die Eigenschaft von Selektorgruppen betrifft, die wir uns zunutze gemacht haben, so gilt sie auch in älteren Browsern. Das Verhalten ist in einem Beispiel als Teil des CSS 1-Abschnitts über vorwärtskompatibles Parsen illustriert. Darüber hinaus schreibt die CSS 2.1-Spezifikation dieses Verhalten dann explizit vor. Um das Alter dieser Spezifikation in Perspektive zu setzen, ist dies diejenige, die :hover eingeführt hat. Kurz gesagt, obwohl diese Technik nicht umfassend in den ältesten oder obskursten Browsern getestet wurde, sollte ihre Unterstützung extrem breit sein.

Zuletzt gibt es noch eine kleine Einschränkung für Sass-Benutzer (Sass, nicht SCSS): Bei der Begegnung mit dem Selektor :not(*):placeholder-shown wird der Compiler durch den führenden Doppelpunkt getäuscht, versucht, ihn als Eigenschaft zu parsen, und bei der Begegnung mit dem Fehler rät er dem Entwickler, den Selektor wie folgt zu escapen: \:not(*):placeholder-shown, was nicht sehr ansprechend aussieht. Eine bessere Problemumgehung ist vielleicht, den Backslash durch einen weiteren universellen Selektor zu ersetzen, um *:not(*):placeholder-shown zu erhalten, da gemäß der Spezifikation er in diesem Fall ohnehin impliziert ist.