Methoden zum Kontrastieren von Text vor Hintergründen

Avatar of Ana Tudor
Ana Tudor am

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

Es begann damit, dass ich mir einen aktuellen Pen von Mandy Michael mit ihren Text-Effekt-Demos ansah. Ich bin ein sehr visueller Mensch, daher bemerkte ich zuerst den Effekt und nicht den Titel (der klar angibt, wie der Effekt erzielt wurde). Sofort dachte ich: „Blend-Modi!“, was sich als falsch herausstellte.

Die Demo verwendet tatsächlich clip-path. Zuerst wird der Text dupliziert. Wir haben schwarzen Text darunter als eigentlichen Textinhalt des Elements und den weißen Text darüber als Wert der content-Eigenschaft (entnommen aus einem Data-Attribut, das über JS aktualisiert wird). Diese beiden werden übereinander gestapelt (sie überlappen sich vollständig). Dann wird das Pseudoelement mit dem weißen Text oben auf die Form des schwarzen Kleides zugeschnitten.

Das bedeutet jedoch, dass wir den Clipping-Pfad ändern müssen, wenn wir das Bild ändern, und zu diesem Zeitpunkt ist es alles andere als einfach, polygonale Clipping-Pfade mit vielen Punkten über Entwicklertools herauszufinden (weshalb etwas wie Benett Feelys Clippy mit zweiseitiger Bearbeitung direkt in Entwicklertools immens nützlich wäre). Also beschloss ich, meine ursprüngliche Idee – Blend-Modi – auszuprobieren.

Nehmen wir an, wir haben eine Überschrift in einem contentEditable1-Container mit einem schwarzen und weißen Bild (naja, Graustufen) als background. Die HTML-Struktur ist wie folgt:

<header>
  <h2 contentEditable role='textbox' aria-multiline='true'>And stay alive</h2>
</header>

Wir setzen das background-image auf den header-Container, geben dem h2 weißen Text und setzen seinen mix-blend-mode auf difference oder exclusion. Das relevante CSS ist unten:

header { background: url(black-and-white-image.jpg) }

h2 {
  color: white;
  mix-blend-mode: difference;
}

Ich kann Blend-Modi nicht wirklich verstehen oder normalerweise einen Unterschied zwischen difference und exclusion erkennen, aber laut MDN sind sie ziemlich gleich, wobei exclusion weniger Kontrast hat. Was ich in dieser Situation verstehen kann, ist, dass weißer Text über dem Bild ein Ergebnis liefert, das dem invertierten Bild entspricht, wo der Text es überlappt. Für einfache Schwarz-Weiß-Bilder wird Schwarz im Originalbild zu Weiß dort, wo wir weißen Text darüber haben, und Weiß im Originalbild wird zu Schwarz dort, wo wir weißen Text darüber haben.

Das Ergebnis ist in folgendem Pen zu sehen

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Mission erfüllt! Sowohl der Text als auch das Bild können geändert werden und der Effekt bleibt erhalten, ohne dass JavaScript oder Änderungen am CSS erforderlich sind.

Aber ich stellte sofort fest, dass es noch ein weiteres Problem gab: Was passiert, wenn das Bild nicht nur schwarz-weiß ist? Nun, versuchen wir das! Es stellt sich heraus, dass das Ergebnis tatsächlich ziemlich gut aussieht.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Der Text ist jedoch nicht mehr Graustufen, und das Setzen von filter: grayscale(1) ändert nichts. Funktioniert irgendein filter-Wert? Nun, ich habe als nächstes drop-shadow() ausprobiert, um eine Antwort auf diese Frage zu finden. Und die Antwort ist, dass drop-shadow() funktioniert, aber die Art und Weise, wie es funktioniert – der Schatten wird mit dem header-Hintergrund vermischt2 – gibt einen Hinweis darauf, warum grayscale() nichts geändert hat: Filter werden *vor* dem Mischen angewendet, unser Text ist weiß und die Ausgabe von grayscale() ist in diesem Fall identisch mit seiner Eingabe (weiß rein, weiß raus). Und da sich vor dem Mischen nichts ändert, ist das Ergebnis dasselbe.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Das hätte wahrscheinlich keine Überraschung sein dürfen. Ich bin schon früher auf Probleme gestoßen, die durch die Reihenfolge der Eigenschaften verursacht wurden.

Zum Beispiel wird filter auch *vor* clip-path angewendet. Wenn wir also ein Element auf eine nicht-rechteckige Form zuschneiden und einen Schatten darauf haben wollen, Pech gehabt, das Setzen des filter auf demselben Element liefert nicht das erwartete Ergebnis. Das liegt daran, dass das rechteckige Element den filter angewendet bekommt, sodass wir einen Schatten um seine rechteckige Box haben und *erst danach* wird es auf die Form zugeschnitten, die über clip-path angegeben wurde. Dies ist immer die Reihenfolge, in der diese beiden angewendet werden, unabhängig von der Reihenfolge, in der Sie sie in CSS festlegen.

Diagram. A box representing an element (left) 
has a CSS filter applied to become a box with a box-shadow (center) then has a clip-path applied to 
become a star with no shadow (right).
Diagramm, wie Browser filter und clip-path anwenden, wenn sie auf dasselbe Element gesetzt sind.

Die Lösung für das filter + clip-path-Problem besteht darin, den filter auf ein Elternelement zu setzen, bei dem nichts sichtbar ist außer dem zugeschnittenen Kind. Der Teil „nichts sichtbar“ ist wichtig, denn wenn das Elternteil sichtbaren Text oder Ränder hat, bekommen diese ebenfalls den Schatten, während wenn es einen Hintergrund hat, der gesamte Hintergrundbereich den Schatten erhält.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Diese Lösung funktioniert jedoch nicht auch für das filter + mix-blend-mode-Problem. Wenn wir unseren h2 in ein div einwickeln und filter auf dieses div setzen, bricht dies den Mischungseffekt.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Schlimmer noch, ich kann mir nichts einfallen lassen, um dieses Problem zu umgehen. Es mag einen Weg geben, dies durch Duplizieren des Textes und Verwendung eines luminosity-Blend-Modus zu erreichen, aber wie bereits erwähnt, verstehe ich Blend-Modi nicht wirklich und habe es nicht geschafft, das richtig hinzubekommen.

Aber vielleicht könnten wir einen völlig anderen Ansatz versuchen, der kein Mischen verwendet. All das Spielen mit Filtern brachte mich auf die Idee, das Bild auf den Text anzuwenden, dann einen invert()-Filter anzuwenden, an den wir grayscale(), contrast() und mehr anhängen könnten.

Mit dieser Methode, die background-clip: text verwendet, haben wir auch den Vorteil einer browserübergreifenden Lösung, da Firefox und Edge dies inzwischen auch implementiert haben.

Der Weg, wie wir das machen, ist folgender: Wir stellen sicher, dass der header und das h2 identische Hintergründe haben und dass sich diese Hintergründe perfekt überlappen. Dann setzen wir color: transparent auf das h2 und schneiden seinen Hintergrund auf text zu. Der letzte Schritt ist das Setzen von filter: invert(1) auf das h2. Das relevante CSS3 ist wie folgt:

h2 {
  background: inherit;
  background-clip: text;
  color: transparent;
  filter: invert(1);
}

Das Ergebnis ist in folgendem Pen zu sehen

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Es sieht aus wie zuvor, außer dass es eine andere Methode verwendet und wir jetzt mehr Funktionen an die filter-Eigenschaft anhängen können. Zum Beispiel können wir den Text in Graustufen umwandeln und seinen Kontrast erhöhen.

filter: invert(1) grayscale(1) contrast(9)

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Das Ändern des filter-Wertes ist auch, wie wir einen Textschatten hinzufügen. Während wir im ersten Fall (mit mix-blend-mode) einfach die text-shadow-Eigenschaft setzen können (und der Schatten ebenfalls mit dem Hintergrund gemischt wird), bricht das Setzen im zweiten Fall die Dinge.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Glücklicherweise können wir drop-shadow() an unseren Filter anhängen.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Der untenstehende Pen zeigt die beiden in diesem Artikel beschriebenen Methoden. Links haben wir die mix-blend-mode-Methode und rechts die background-clip- und filter-Methode (mit den zusätzlichen Graustufen- und Kontrastkomponenten). Der Text ist editierbar und die Thumbnails ermöglichen das Ändern des background-image.

Siehe den Pen von thebabydino (@thebabydino) auf CodePen.

Also, wenn wir diese Methoden gegeneinander ausspielen, welche ist besser?

Auf Ästhetik gehen wir nicht ein, da dies eine subjektive Angelegenheit ist und wahrscheinlich von Fall zu Fall und sogar von Stimmung zu Stimmung unterschiedlich ist.

Was die grundlegende Unterstützung angeht, hat die letzte Methode einen Vorteil, da sie in Edge unterstützt wird, während mix-blend-mode dies nicht tut (aber wenn Sie sie in Edge wünschen, stimmen Sie dafür, denn Ihr Feedback zählt). Mandys Originalbeispiel verwendet clip-path, eine weitere sehr nützliche Funktion, die leider auch nicht in Edge funktioniert (Sie können hier dafür abstimmen).

Wenn es um die Auswahl von Text geht, funktioniert die mix-blend-mode-Methode perfekt und sieht in den unterstützten Browsern am besten aus.

The editable text, made to contrast with the background image using the mix-blend-mode method, shown selected in its entirety. The selection background is blended with the image underneath.
Textauswahl bei Verwendung der mix-blend-mode-Methode

Bei der Verwendung der clip-path-Methode sieht die Auswahl sauber aus und der Text bleibt lesbar, aber sie schien in Firefox zu stolpern, als ich über dem Pseudoelement-Text war. Glücklicherweise erwies sich dieses Problem als leicht zu beheben: Setzen von pointer-events: none auf dem Pseudoelement.

Animated gif. The editable text, made to contrast with the background image using the clip-path method, gets selected in Firefox. The selection stumbles where the copy of this text, generated via a pseudo-element, overlaps the original. Setting pointer-events: none on the pseudo-element fixes this problem.
Textauswahl in Firefox bei Verwendung der clip-path-Methode

Was die letzte Methode angeht, kann die Auswahl unschön aussehen und den Text schwerer lesbar machen. Ganz zu schweigen davon, dass der Schatten in diesem Fall auf das gesamte Auswahlrechteck angewendet wird und nicht nur auf den eigentlichen Text.

The editable text, made to contrast with the background image using the background-clip: text + filter method, shown selected in its entirety. The whole selection box has the filter effect applied, not just the text, so the selection background is grayscale and the drop-shadow is now on the selection box, not just on the text itself.
Textauswahl bei Verwendung der background-clip: text + filter-Methode

Auch das Ändern von Text wird in Firefox bei der letzten Methode umständlich, der Bildschirm wird beim Tippen neuer Texte „schmutzig“.

Animated gif. The editable text, made to contrast with the background image using the background-clip: text + filter method, gets edited in Firefox. As you write, the text becomes clipped and garbled, overlapping itself.
Textänderung in Firefox bei Verwendung der background-clip: text + filter-Methode

Die clip-path-Methode hatte anfangs auch ein Problem mit der Änderung von Text in Firefox: Der Versuch, die Bearbeitung mitten im Pseudoelement-Text zu beginnen, funktionierte nicht. Glücklicherweise behebt das Setzen von pointer-events: none auf dem Pseudoelement auch dieses Problem.

Animated gif. The editable text, made to contrast with the background image using the clip-path method, gets edited in Firefox. The cursor cannot be placed to start editing from the part where the copy of this text, generated via a pseudo-element, overlaps the original. Setting pointer-events: none on the pseudo-element fixes this problem.
Textänderung in Firefox bei Verwendung der clip-path-Methode

Was die Flexibilität bei der Textgestaltung angeht, schneidet die letzte Methode am besten ab, da das Anhängen von Filterfunktionen uns weit bringen kann.

Letztendlich hängt die Frage, welche besser ist, vom jeweiligen Anwendungsfall ab. Welche Browser sollen unterstützt werden? Ändert sich das Bild? Ändert sich der Text? Wie soll der Text visuell verändert werden?


1 contentEditable ist nicht in allen Browsern standardmäßig zugänglich, daher müssen wir Semantik bereitstellen, um das zu beheben.

2 Achten Sie bei Experimenten mit Blend-Modi auf die Lesbarkeitsprobleme, die sie verursachen können, insbesondere in Bezug auf den Kontrast. WCAG 2.0 besagt, dass, es sei denn, der Text bildet Teil rein dekorativer Bilder, er einen Mindestkontrastschwellenwert bestehen sollte. Werkzeuge wie Color ContrastAnalyzer können Ihnen helfen, diese Anforderung zu erfüllen.

3 Präfixe werden zur Kürze weggelassen, aber background-clip benötigt immer noch das -webkit--Präfix für WebKit-Browser (Firefox und Edge haben es ungeprägt implementiert, obwohl beide es auch mit dem -webkit--Präfix unterstützen, wahrscheinlich weil es bereits so stark mit diesem Präfix verwendet wurde) und dieses Präfix muss manuell/über eine Präprozessor-Mixin hinzugefügt werden, was etwas ist, das ich normalerweise nicht empfehle, aber in diesem speziellen Fall geschieht das automatische Präfixierung über Autoprefixer oder Prefixfree nicht (Prefixfree funktioniert über Feature-Erkennung, die keine Eigenschaften wie background-clip, filter oder clip-path erfasst, und dies ist das Autoprefixer-Problem). Was filter betrifft, so wird es jetzt ungeprägt in allen aktuellen Desktop-Browsern unterstützt und Autoprefixer kann es trotzdem präfixen.