Verwischte Ränder in CSS

Avatar of Ana Tudor
Ana Tudor am

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

Nehmen wir an, wir möchten ein Element ansprechen und nur den Rand visuell verwischen. Es gibt keine einfache, integrierte Funktion der Webplattform, auf die wir zurückgreifen können. Aber mit ein wenig CSS-Tricksen können wir es schaffen.

Hier ist, was wir erreichen wollen

Screenshot of an element with a background image that shows oranges on a wooden table. The border of this element is blurred.
Das gewünschte Ergebnis.

Sehen wir uns an, wie wir diesen Effekt codieren können, wie wir ihn mit abgerundeten Ecken verbessern können, wie wir die Unterstützung erweitern können, damit er browserübergreifend funktioniert, was die Zukunft in diesem Bereich bringt und welche anderen interessanten Ergebnisse wir mit der gleichen Idee erzielen können!

Den grundlegenden verwischten Rand codieren

Wir beginnen mit einem Element, dem wir einige Dummy-Dimensionen, einen teilweise transparenten (nur leicht sichtbaren) border und einen background geben, dessen Größe relativ zur border-box ist, dessen Sichtbarkeit wir aber auf die padding-box beschränken.

$b: 1.5em; // border-width

div {
  border: solid $b rgba(#000, .2);
  height: 50vmin;
  max-width: 13em;
  max-height: 7em;
  background: url(oranges.jpg) 50%/ cover 
                border-box /* background-origin */
                padding-box /* background-clip */;
}

Die von background-origin spezifizierte Box ist die Box, deren oberer linker Eckpunkt der 0 0 Punkt für background-position ist und auf die sich auch background-size (in unserem Fall auf cover gesetzt) bezieht. Die von background-clip spezifizierte Box ist die Box, innerhalb deren Grenzen der background sichtbar ist.

Die anfänglichen Werte sind padding-box für background-origin und border-box für background-clip, daher müssen wir beide in diesem Fall angeben.

Wenn Sie eine eingehendere Auffrischung zu background-origin und background-clip benötigen, können Sie diesen detaillierten Artikel zu diesem Thema lesen.

Der obige Code ergibt folgendes Ergebnis

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

Als Nächstes fügen wir ein absolut positioniertes Pseudoelement hinzu, das die gesamte border-box seines Elternelements abdeckt und dahinter positioniert ist (z-index: -1). Wir lassen dieses Pseudoelement auch den border und background seines Elternelements erben, ändern dann die border-color auf transparent und background-clip auf border-box.

$b: 1.5em; // border-width

div {
  position: relative;
  /* same styles as before */
  
  &:before {
    position: absolute;
    z-index: -1;
    /* go outside padding-box by 
     * a border-width ($b) in every direction */
    top: -$b; right: -$b; bottom: -$b; left: -$b;
    border: inherit;
    border-color: transparent;
    background: inherit;
    background-clip: border-box;
    content: ''
  }
}

Nun können wir auch den background hinter dem kaum sichtbaren border sehen.

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

Nun, Sie sehen vielleicht schon, worauf das hinausläuft! Der nächste Schritt ist, das Pseudoelement mit blur() zu verwischen. Da dieses Pseudoelement nur unter dem teilweise transparenten border sichtbar ist (der Rest wird vom padding-box-beschränkten background seines Elternelements abgedeckt), ist der Bereich des border der einzige Bereich des Bildes, den wir verwischt sehen.

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

Wir haben auch die Transparenz der border-color des Elements auf .03 reduziert, da wir möchten, dass die Weichzeichnung den größten Teil der Arbeit leistet, um hervorzuheben, wo sich der border befindet.

Das mag wie fertig aussehen, aber etwas gefällt mir immer noch nicht: die Ränder des Pseudoelements sind jetzt ebenfalls verwischt. Lassen Sie uns das also beheben!

Eine praktische Sache bei der Reihenfolge, in der Browser Eigenschaften anwenden, ist, dass Filter angewendet werden, bevor Clipping. Das ist zwar nicht das, was wir wollen und führt uns in vielen anderen Fällen zu unbequemen Workarounds... hier erweist es sich als sehr nützlich!

Das bedeutet, dass wir es nach dem Verzerren des Pseudoelements auf seine border-box zuschneiden können!

Meine bevorzugte Methode hierfür ist die Einstellung von clip-path auf inset(0), denn... es ist wirklich die einfachste Methode! polygon(0 0, 100% 0, 100% 100%, 0 100%) wäre übertrieben.

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

Falls Sie sich fragen, warum wir clip-path nicht auf das eigentliche Element setzen, anstatt es auf das :before Pseudoelement anzuwenden, liegt das daran, dass das Setzen von clip-path auf das Element dieses zu einem Stapelkontext machen würde. Dies würde alle seine untergeordneten Elemente (und folglich auch sein verwischtes :before Pseudoelement) zwingen, darin enthalten zu sein und somit vor seinem background zu liegen. Und dann könnte keine noch so mächtige z-index- oder !important-Einstellung das ändern.

Wir können das aufhübschen, indem wir etwas Text mit einer schöneren font, einem box-shadow und einigen Layout-Eigenschaften hinzufügen.

Was ist mit abgerundeten Ecken?

Das Beste an der Verwendung von inset() anstelle von polygon() für clip-path ist, dass inset() auch für jede border-radius, die wir wünschen, geeignet ist!

Und wenn ich sage, jede border-radius, dann meine ich das! Schauen Sie sich das an!

div {
  --r: 15% 75px 35vh 13vw/ 3em 5rem 29vmin 12.5vmax;
  border-radius: var(--r);
  /* same styles as before */
  
  &:before {
    /* same styles as before */
    border-radius: inherit;
    clip-path: inset(0 round var(--r));
  }
}

Es funktioniert wie ein Zauber!

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

Unterstützung erweitern

Einige mobile Browser benötigen immer noch das Präfix -webkit- für sowohl filter als auch clip-path, also stellen Sie sicher, dass Sie auch diese Versionen mit einbeziehen. Beachten Sie, dass sie in den hier eingebetteten CodePen-Demos enthalten sind, auch wenn ich sie im Code, der im Hauptteil dieses Artikels vorgestellt wird, übersprungen habe.

In Ordnung, aber was ist, wenn wir Edge unterstützen müssen? clip-path funktioniert in Edge nicht, aber filter schon, was bedeutet, dass wir den verwischten Rand erhalten, aber keine scharfen Schnittbegrenzungen.

Nun, wenn wir keine abgerundeten Ecken benötigen, können wir die veraltete clip-Eigenschaft als Fallback verwenden. Das bedeutet, dass Sie diese Zeile direkt vor den clip-path-Zeilen hinzufügen

clip: rect(0 100% 100% 0)

Und unsere Demo funktioniert jetzt in Edge... irgendwie! Die rechten, unteren und linken Ränder sind scharf abgeschnitten, aber der obere bleibt immer noch verwischt (nur im Debug-Modus des Pens, im iFrame in der Editoransicht scheint alles in Ordnung zu sein). Und das Öffnen von DevTools oder ein Rechtsklick im Edge-Fenster oder das Klicken irgendwo außerhalb dieses Fensters lässt die Auswirkung dieser Eigenschaft verschwinden. Bug des Monats, direkt da!

Nun, da dies so unzuverlässig ist und uns auch nicht hilft, wenn wir abgerundete Ecken wünschen, versuchen wir einen anderen Ansatz!

Das ist ein bisschen so, als würde man sich hinter dem linken Ohr mit dem rechten Fuß kratzen (oder umgekehrt, je nachdem, welche Seite beweglicher ist), aber es ist die einzige Möglichkeit, wie ich es mir vorstellen kann, damit es in Edge funktioniert.

Einige von Ihnen schreien vielleicht schon etwas wie "aber Ana... overflow: hidden!" und ja, das ist es, was wir jetzt anstreben. Ich habe es anfangs vermieden, wegen der Art und Weise, wie es funktioniert: es schneidet alle Nachfahren-Inhalte außerhalb der padding-box aus. Nicht außerhalb der border-box, wie wir es durch Clipping getan haben!

Das bedeutet, wir müssen den echten border aufgeben und ihn mit padding emulieren, was mir nicht gerade gefällt, da es zu weiteren Komplikationen führen kann, aber gehen wir es Schritt für Schritt an!

Was die Codeänderungen betrifft, so entfernen wir zunächst alle border-bezogenen Eigenschaften und setzen den Wert von border-width als padding. Dann setzen wir overflow: hidden und beschränken den background des eigentlichen Elements auf die content-box. Schließlich setzen wir den background-clip des Pseudoelements auf den Wert padding-box zurück und setzen seine Offsets auf Null.

$fake-b: 1.5em; // fake border-width

div {
  /* same styles as before */
  overflow: hidden;
  padding: $fake-b;
  background: url(oranges.jpg) 50%/ cover 
                padding-box /* background-origin */
                content-box /* background-clip */;
  
  &:before {
    /* same styles as before */
    top: 0; right: 0; bottom: 0; left: 0;
    background: inherit;
    background-clip: padding-box;
  }
}

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

Wenn wir diese kaum sichtbare "Rand"-Überlagerung wünschen, benötigen wir eine weitere background-Ebene auf dem eigentlichen Element.

$fake-b: 1.5em; // fake border-width
$c: rgba(#000, .03);

div {
  /* same styles as before */
  overflow: hidden;
  padding: $fake-b;
  --img: url(oranges.jpg) 50%/ cover;
  background: var(--img)
                padding-box /* background-origin */
                content-box /* background-clip */,  
              linear-gradient($c, $c);
  
  &:before {
    /* same styles as before */
    top: 0; right: 0; bottom: 0; left: 0;
    background: var(--img);
  }
}

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

Wir können auch problemlos abgerundete Ecken hinzufügen.

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

Warum haben wir das nicht von Anfang an gemacht?!

Erinnern Sie sich, als ich vorhin sagte, dass die Nichtverwendung eines tatsächlichen border später zu Komplikationen führen kann?

Nun, nehmen wir an, wir möchten etwas Text haben. Bei der ersten Methode, die einen tatsächlichen border und clip-path verwendet, reicht es aus, einen padding (z. B. 1em) auf unser Element anzuwenden, um zu verhindern, dass der Textinhalt den verwischten border berührt.

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

Aber bei der Methode overflow: hidden haben wir die padding-Eigenschaft bereits verwendet, um den verwischten "Rand" zu erstellen. Das Erhöhen seines Wertes hilft nicht, da es nur die Breite des gefälschten Rands erhöht.

Wir könnten den Text in ein untergeordnetes Element einfügen. Oder wir könnten auch das :after Pseudoelement verwenden!

Die Funktionsweise ist ziemlich ähnlich zur ersten Methode, wobei das :after das eigentliche Element ersetzt. Der Unterschied besteht darin, dass wir die verwischten Ränder mit overflow: hidden anstatt mit clip-path: inset(0) zuschneiden und das padding auf dem eigentlichen Element die border-width ($b) des Pseudoelements plus den gewünschten padding-Wert ist.

$b: 1.5em; // border-width

div {
  overflow: hidden;
  position: relative;
  padding: calc(1em + #{$b});
  /* prettifying styles */
	
  &:before, &:after {
    position: absolute;
    z-index: -1; /* put them *behind* parent */
    /* zero all offsets */
    top: 0; right: 0; bottom: 0; left: 0;
    border: solid $b rgba(#000, .03);
    background: url(oranges.jpg) 50%/ cover 
                  border-box /* background-origin */
                  padding-box /* background-clip */;
    content: ''
  }
	
  &:before {
    border-color: transparent;
    background-clip: border-box;
    filter: blur(9px);
  }
}

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

Was ist mit Text und sehr stark abgerundeten Ecken? Nun, das werden wir in einem anderen Artikel besprechen – bleiben Sie dran!

Was ist mit backdrop-filter?

Einige von Ihnen fragen sich vielleicht (wie ich, als ich anfing, mit verschiedenen Ideen zu experimentieren, um diesen Effekt zu erzielen), ob backdrop-filter keine Option ist.

Nun, ja und nein!

Technisch gesehen ist es möglich, den gleichen Effekt zu erzielen, aber da Firefox ihn noch nicht implementiert, schneiden wir die Firefox-Unterstützung aus, wenn wir diesen Weg wählen. Ganz zu schweigen davon, dass dieser Ansatz uns auch zwingt, beide Pseudoelemente zu verwenden, wenn wir die bestmögliche Unterstützung für den Fall wünschen, dass unser Element Textinhalt hat (was bedeutet, dass wir die Pseudoelemente und ihren padding-box-Bereich background darunter benötigen).

Update: Aufgrund einer Regression funktioniert die backdrop-filter-Technik in Chrome nicht mehr, sodass die Unterstützung bestenfalls auf Safari und Edge beschränkt ist.

Für diejenigen, die noch nicht wissen, was backdrop-filter tut: Es filtert das, was durch die (teilweise) transparenten Teile des Elements, auf das es angewendet wird, sichtbar ist.

Wir müssen wie folgt vorgehen: Beide Pseudoelemente haben einen transparenten border und einen background, der relativ zur padding-box positioniert und bemessen ist. Wir beschränken den background des Pseudoelements oben (:after) auf die padding-box.

Nun hat :after im Bereich des border keinen background mehr, und wir können durch das :before Pseudoelement dahinter hindurchsehen. Wir setzen einen backdrop-filter auf :after und ändern vielleicht sogar die border-color von transparent zu leicht sichtbar. Der background des unteren (:before) Pseudoelements, der durch den (teilweise) transparenten, kaum unterscheidbaren border von :after darüber noch sichtbar ist, wird durch die Anwendung von backdrop-filter verwischt.

$b: 1.5em; // border-width

div {
  overflow: hidden;
  position: relative;
  padding: calc(1em + #{$b});
  /* prettifying styles */
	
  &:before, &:after {
    position: absolute;
    z-index: -1; /* put them *behind* parent */
    /* zero all offsets */
    top: 0; right: 0; bottom: 0; left: 0;
    border: solid $b transparent;
    background: $url 50%/ cover 
                  /* background-origin & -clip */
                  border-box;
    content: ''
  }
	
  &:after {
    border-color: rgba(#000, .03);
    background-clip: padding-box;
    backdrop-filter: blur(9px); /* no Firefox support */
  }
}

Denken Sie daran, dass das Live-Demo dafür derzeit nicht in Firefox funktioniert und das Flag Experimental Web Platform features in chrome://flags aktiviert sein muss, damit es in Chrome funktioniert.

Ein Pseudoelement eliminieren

Das ist etwas, das ich in der Praxis nicht empfehlen würde, da es auch die Edge-Unterstützung ausschaltet, aber wir haben eine Möglichkeit, das gewünschte Ergebnis mit nur einem Pseudoelement zu erzielen.

Wir beginnen damit, das Bild-background auf dem Element zu setzen (wir müssen keinen expliziten border setzen, solange wir seine Breite in das padding einbeziehen) und dann einen teilweise transparenten, kaum sichtbaren background auf dem absolut positionierten Pseudoelement, das sein gesamtes Elternelement abdeckt. Wir setzen auch den backdrop-filter auf dieses Pseudoelement.

$b: 1.5em; // border-width

div {
  position: relative;
  padding: calc(1em + #{$b});
  background: url(oranges.jpg) 50%/ cover;
  /* prettifying styles */
	
  &:before {
    position: absolute;
    /* zero all offsets */
    top: 0; right: 0; bottom: 0; left: 0;
    background: rgba(#000, .03);
    backdrop-filter: blur(9px); /* no Firefox support */
    content: ''
  }
}

Nun gut, aber das verwischt das gesamte Element hinter dem fast transparenten Pseudoelement, einschließlich seines Textes. Und es ist kein Fehler, das ist es, was backdrop-filter tun soll.

Screenshot.
Das Problem.

Um dies zu beheben, müssen wir den inneren Rechteck (dessen Ränder einen Abstand von $b von den border-box-Rändern haben) des Pseudoelements entfernen (nicht transparent machen, das ist in diesem Fall völlig nutzlos).

Dafür haben wir zwei Möglichkeiten.

Der erste Weg (Live-Demo) ist mit clip-path und der Zero-Width-Tunnel-Technik.

$b: 1.5em; // border-width
$o: calc(100% - #{$b});

div {
  /* same styles as before */
	
  &:before {
    /* same styles as before */

    /* doesn't work in Edge */
    clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 
                       0 0, 
                       #{$b $b}, #{$b $o}, #{$o $o}, #{$o $b}, 
                       #{$b $b});
  }
}

Der zweite Weg (Live-Demo) besteht aus zwei kompositierten mask-Ebenen (beachten Sie, dass wir in diesem Fall explizit einen border für unser Pseudoelement setzen müssen).

$b: 1.5em; // border-width

div {
  /* same styles as before */
	
  &:before {
    /* same styles as before */

    border: solid $b transparent;

    /* doesn't work in Edge */
    --fill: linear-gradient(red, red);
    -webkit-mask: var(--fill) padding-box, 
                  var(--fill);
    -webkit-mask-composite: xor;
            mask: var(--fill) padding-box exclude, 
                  var(--fill);
  }
}

Da keine dieser beiden Eigenschaften in Edge funktioniert, bedeutet dies, dass die Unterstützung nun auf WebKit-Browser beschränkt ist (und wir müssen immer noch das Flag "Experimental Web Platform features" aktivieren, damit backdrop-filter in Chrome funktioniert).

Zukunft (und bessere!) Lösung

Die Funktion filter() erlaubt es uns, Filter auf einzelne background-Ebenen anzuwenden. Dies eliminiert die Notwendigkeit eines Pseudoelements und reduziert den Code, der für die Erzielung dieses Effekts benötigt wird, auf zwei CSS-Deklarationen!

border: solid 1.5em rgba(#000, .03);
background: $url 
              border-box /* background-origin */
              padding-box /* background-clip */, 
            filter($url, blur(9px)) 
              /* background-origin & background-clip */
              border-box

Wie Sie vielleicht erraten haben, ist das Problem hier die Unterstützung. Safari ist der einzige Browser, der sie derzeit implementiert, aber wenn Sie der Meinung sind, dass die Funktion filter() Ihnen helfen könnte, können Sie Ihre Anwendungsfälle hinzufügen und den Implementierungsfortschritt für Chrome und Firefox verfolgen.

Mehr Optionen für Randfilter

Ich habe bisher nur über das Verzerren des border gesprochen, aber diese Technik funktioniert für praktisch jeden CSS-filter (außer drop-shadow(), was in diesem Kontext nicht viel Sinn ergeben würde). Sie können mit dem Wechseln zwischen ihnen und dem Tweaken von Werten in der interaktiven Demo unten spielen.

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

Und alles, was wir bisher getan haben, hat nur eine filter-Funktion verwendet, aber wir können sie auch verketten, und dann sind die Möglichkeiten endlos – welche coolen Effekte können Sie so erzielen?

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