Ich habe kürzlich eine interessante Änderung auf CodePen bemerkt: Wenn man mit der Maus über die Pens auf der Homepage fährt, erweitert sich im Hintergrund ein Rechteck mit abgerundeten Ecken.

Als neugieriges Wesen, das ich nun mal bin, musste ich nachsehen, wie das funktioniert! Es stellt sich heraus, dass das Rechteck im Hintergrund ein absolut positioniertes ::after Pseudoelement ist.

::after-Stile. Ein positiver Offset geht von der padding-Grenze des Elternteils nach innen, während ein negativer nach außen geht.Bei :hover werden seine Offsets überschrieben und in Kombination mit der transition erhalten wir den Effekt der sich erweiternden Box.

::after-Stile bei :hover.Die Eigenschaft right hat sowohl in den ursprünglichen als auch in den :hover-Regelsätzen den gleichen Wert (-1rem), daher ist es unnötig, sie zu überschreiben. Alle anderen Offsets bewegen sich um 2rem nach außen (von 1rem auf -1rem für die top- und left-Offsets und von -1rem auf -3rem für den bottom-Offset).
Eine Sache, die hier auffällt, ist, dass das ::after-Pseudoelement einen border-radius von 10px hat, der beim Erweitern erhalten bleibt. Das brachte mich zum Nachdenken darüber, welche Methoden wir haben, um (Pseudo-)Elemente zu erweitern/schrumpfen und dabei ihren border-radius zu erhalten. Wie viele fallen Ihnen ein? Lassen Sie es mich wissen, wenn Sie Ideen haben, die unten nicht aufgeführt sind, wo wir uns eine Reihe von Optionen ansehen und sehen, welche für welche Situation am besten geeignet ist.
Ändern der Offsets
Dies ist die Methode, die auf CodePen verwendet wird, und sie funktioniert aus einer Reihe von Gründen in dieser speziellen Situation wirklich gut. Erstens hat sie eine hervorragende Unterstützung. Sie funktioniert auch, wenn das sich erweiternde (Pseudo-)Element responsiv ist, ohne feste Abmessungen, und gleichzeitig der Erweiterungsbetrag fest ist (ein rem-Wert). Sie funktioniert auch für die Erweiterung in mehr als zwei Richtungen (top, bottom und left in diesem speziellen Fall).
Es gibt jedoch ein paar Vorbehalte, die wir beachten müssen.
Erstens kann unser sich erweiterndes Element nicht position: static haben. Dies ist im Kontext des CodePen-Anwendungsfalls kein Problem, da das ::after-Pseudoelement sowieso absolut positioniert sein muss, um unter dem Rest des Inhalts seines Elternteils platziert zu werden.
Zweitens kann eine Übertreibung bei Offset-Animationen (sowie generell bei der Animation von Eigenschaften, die das Layout mit Box-Eigenschaften beeinflussen, wie Offsets, Margins, Randbreiten, Paddings oder Abmessungen) die Leistung negativ beeinflussen. Auch hier ist das kein Problem, wir haben nur einen kleinen transition bei :hover, keine große Sache.
Ändern der Abmessungen
Anstatt Offsets zu ändern, könnten wir stattdessen Abmessungen ändern. Dies ist jedoch eine Methode, die funktioniert, wenn wir möchten, dass sich unser (Pseudo-)Element in maximal zwei Richtungen erweitert. Andernfalls müssen wir auch Offsets ändern. Um dies besser zu verstehen, betrachten wir die CodePen-Situation, in der wir möchten, dass sich unsere ::after-Pseudoelemente in drei Richtungen (top, bottom und left) erweitern.
Die relevanten ursprünglichen Größeninformationen sind folgende:
.single-item::after {
top: 1rem;
right: -1rem;
bottom: -1rem;
left: 1rem;
}
Da sich gegenüberliegende Offsets (die Paare top–bottom und left–right) gegenseitig aufheben (1rem - 1rem = 0), ergeben sich daraus die Abmessungen des Pseudoelements, die denen seines Elternteils entsprechen (oder 100% der Abmessungen des Elternteils).
Wir können das obige also umschreiben als:
.single-item::after {
top: 1rem;
right: -1rem;
width: 100%;
height: 100%;
}
Bei :hover erhöhen wir die width um 2rem nach links und die height um 4rem, 2rem nach oben und 2rem nach unten. Aber nur das Schreiben von:
.single-item::after {
width: calc(100% + 2rem);
height: calc(100% + 4rem);
}
… reicht nicht aus, da dies die height um 4rem in Richtung nach unten erhöht, anstatt sie um 2rem nach oben und 2rem nach unten zu erhöhen. Das folgende Demo verdeutlicht dies (setzen Sie :focus auf die Elemente oder fahren Sie mit der Maus darüber, um zu sehen, wie sich das ::after-Pseudoelement erweitert).
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Wir müssten die Eigenschaft top ebenfalls aktualisieren, um den gewünschten Effekt zu erzielen:
.single-item::after {
top: -1rem;
width: calc(100% + 2rem);
height: calc(100% + 4rem);
}
Was funktioniert, wie unten zu sehen ist:
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Aber, ehrlich gesagt, fühlt sich das weniger wünschenswert an als das reine Ändern von Offsets.
Das Ändern von Abmessungen ist jedoch eine gute Lösung für eine andere Art von Situation, z. B. wenn wir Balken mit abgerundeten Ecken haben möchten, die sich in einer einzigen Richtung erweitern/schrumpfen.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Beachten Sie, dass, wenn wir keine abgerundeten Ecken zum Beibehalten hätten, die bessere Lösung die gerichtete Skalierung über die transform-Eigenschaft wäre.
Ändern von Padding/Randbreite
Ähnlich wie beim Ändern der Abmessungen können wir das padding oder die border-width (für einen border, der transparent ist) ändern. Beachten Sie, dass wir, genau wie beim Ändern der Abmessungen, auch die Offsets aktualisieren müssen, wenn die Box in mehr als zwei Dimensionen erweitert wird.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
In der obigen Demo stellt die rosafarbene Box die content-box des ::after-Pseudoelements dar, und Sie können sehen, dass sie gleich groß bleibt, was für diesen Ansatz wichtig ist.
Um zu verstehen, warum das wichtig ist, betrachten wir diese weitere Einschränkung: Wir müssen auch die Box-Abmessungen durch zwei Offsets plus width und height definieren, anstatt alle vier Offsets zu verwenden. Das liegt daran, dass sich die padding/border-width nur nach innen vergrößern würden, wenn wir vier Offsets anstelle von zwei plus width und height verwenden würden.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Aus demselben Grund können wir auf unserem ::after-Pseudoelement nicht box-sizing: border-box haben.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Trotz dieser Einschränkungen kann diese Methode nützlich sein, wenn unser sich erweiterndes (Pseudo-)Element Textinhalt hat, den wir bei :hover nicht herumwandern sehen wollen, wie im folgenden Pen gezeigt, wo die ersten beiden Beispiele Offsets/Abmessungen ändern, während die letzten beiden Padding/Randbreiten ändern.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Ändern des Margins
Mit dieser Methode setzen wir zuerst die Offsets auf die :hover-Zustandswerte und ein margin, um dies zu kompensieren und uns die ursprüngliche Größenordnung zu geben.
.single-item::after {
top: -1rem;
right: -1rem;
bottom: -3rem;
left: -1rem;
margin: 2rem 0 2rem 2rem;
}
Dann setzen wir dieses margin bei :hover auf Null.
.single-item:hover::after { margin: 0 }
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Dies ist ein weiterer Ansatz, der für die CodePen-Situation gut funktioniert, obwohl mir keine anderen Anwendungsfälle einfallen. Beachten Sie auch, dass diese Methode, genau wie das Ändern von Offsets oder Abmessungen, die Größe der content-box beeinflusst, sodass jeder Textinhalt, den wir möglicherweise haben, verschoben und neu angeordnet wird.
Ändern der Schriftgröße
Dies ist wahrscheinlich die kniffligste aller Methoden und hat viele Einschränkungen, die wichtigste davon ist, dass wir keinen Textinhalt auf dem eigentlichen (Pseudo-)Element haben können, das sich erweitert/schrumpft - aber es ist eine weitere Methode, die im CodePen-Fall gut funktionieren würde.
Außerdem bewirkt font-size allein nichts, um eine Box größer oder kleiner zu machen. Wir müssen sie mit einer der zuvor diskutierten Eigenschaften kombinieren.
Zum Beispiel können wir die font-size für ::after auf 1rem einstellen, die Offsets auf den erweiterten Fall einstellen und em-Margins setzen, die dem Unterschied zwischen dem erweiterten und dem ursprünglichen Zustand entsprechen.
.single-item::after {
top: -1rem;
right: -1rem;
bottom: -3rem;
left: -1rem;
margin: 2em 0 2em 2em;
font-size: 1rem;
}
Dann, bei :hover, setzen wir die font-size auf 0.
.single-item:hover::after { font-size: 0 }
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Wir können font-size auch mit Offsets verwenden, obwohl es etwas komplizierter wird.
.single-item::after {
top: calc(2em - 1rem);
right: -1rem;
bottom: calc(2em - 3rem);
left: calc(2em - 1rem);
font-size: 1rem;
}
.single-item:hover::after { font-size: 0 }
Dennoch ist wichtig, dass es funktioniert, wie unten zu sehen ist:
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Die Kombination von font-size mit Abmessungen ist noch haariger, da wir auch den vertikalen Offset-Wert bei :hover zusätzlich zu allem anderen ändern müssen.
.single-item::after {
top: 1rem;
right: -1rem;
width: calc(100% + 2em);
height: calc(100% + 4em);
font-size: 0;
}
.single-item:hover::after {
top: -1rem;
font-size: 1rem
}
Nun ja, zumindest funktioniert es.
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Dasselbe gilt für die Verwendung von font-size mit padding/border-width.
.single-item::after {
top: 1rem;
right: -1rem;
width: 100%;
height: 100%;
font-size: 0;
}
.single-item:nth-child(1)::after {
padding: 2em 0 2em 2em;
}
.single-item:nth-child(2)::after {
border: solid 0 transparent;
border-width: 2em 0 2em 2em;
}
.single-item:hover::after {
top: -1rem;
font-size: 1rem;
}
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Ändern der Skalierung
Wenn Sie Artikel über animation-Performance gelesen haben, dann haben Sie wahrscheinlich gelesen, dass es besser ist, Transfoms zu animieren als Eigenschaften, die das Layout beeinflussen, wie Offsets, Margins, Ränder, Paddings, Abmessungen – ziemlich das, was wir bisher verwendet haben!
Das erste Problem, das hier auffällt, ist, dass das Skalieren eines Elements auch seine Eckabrundung skaliert, wie unten gezeigt:
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Wir können dies umgehen, indem wir auch den border-radius in die entgegengesetzte Richtung skalieren.
Nehmen wir an, wir skalieren ein Element um den Faktor $fx entlang der x-Achse und um den Faktor $fy entlang der y-Achse und wir möchten seinen border-radius bei einem konstanten Wert $r halten.
Das bedeutet, wir müssen $r auch durch den entsprechenden Skalierungsfaktor entlang jeder Achse dividieren.
border-radius: #{$r/$fx}/ #{$r/$fy};
transform: scale($fx, $fy)
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Beachten Sie jedoch, dass wir bei dieser Methode Skalierungsfaktoren verwenden müssen, nicht die Beträge, um die wir unser (Pseudo-)Element in die eine oder andere Richtung erweitern. Das Ermitteln der Skalierungsfaktoren aus den Abmessungen und Erweiterungsbeträgen ist möglich, aber *nur wenn* diese in Einheiten ausgedrückt sind, die eine bestimmte feste Beziehung zueinander haben. Während Präprozessoren Einheiten wie in oder px mischen können, da 1in immer 96px ist, können sie nicht auflösen, wie viel 1em oder 1% oder 1vmin oder 1ch in px ist, da ihnen der Kontext fehlt. Und calc() ist auch keine Lösung, da es uns nicht erlaubt, einen Längenwert durch einen anderen Längenwert zu teilen, um einen einheitenlosen Skalierungsfaktor zu erhalten.
Deshalb ist die Skalierung keine Lösung im CodePen-Fall, wo die ::after-Boxen Abmessungen haben, die vom Viewport abhängen, und sich gleichzeitig um feste rem-Beträge erweitern.
Aber wenn unser Skalierungsbetrag gegeben ist oder wir ihn leicht berechnen können, ist dies eine Option, die man in Betracht ziehen sollte, insbesondere da die Erstellung der Skalierungsfaktoren zu benutzerdefinierten Eigenschaften, die wir dann mit etwas Houdini-Magie animieren, unseren Code erheblich vereinfachen kann.
border-radius: calc(#{$r}/var(--fx))/ calc(#{$r}/var(--fy));
transform: scale(var(--fx), var(--fy))
Beachten Sie, dass Houdini nur in Chromium-Browsern mit dem Flag Experimental Web Platform features funktioniert.
Zum Beispiel können wir diese Kachelgitter-Animation erstellen:
Die quadratischen Kacheln haben eine Kantenlänge $l und eine Eckabrundung von $k*$l.
.tile {
width: $l;
height: $l;
border-radius: calc(#{$r}/var(--fx))/ calc(#{$r}/var(--fy));
transform: scale(var(--fx), var(--fy))
}
Wir registrieren unsere beiden benutzerdefinierten Eigenschaften:
CSS.registerProperty({
name: '--fx',
syntax: '<number>',
initialValue: 1,
inherits: false
});
CSS.registerProperty({
name: '--fy',
syntax: '<number>',
initialValue: 1,
inherits: false
});
Und wir können sie dann animieren:
.tile {
/* same as before */
animation: a $t infinite ease-in alternate;
animation-name: fx, fy;
}
@keyframes fx {
0%, 35% { --fx: 1 }
50%, 100% { --fx: #{2*$k} }
}
@keyframes fy {
0%, 35% { --fy: 1 }
50%, 100% { --fy: #{2*$k} }
}
Schließlich fügen wir eine Verzögerung hinzu, die von den horizontalen (--i) und vertikalen (--j) Gitterindizes abhängt, um einen gestaffelten animation-Effekt zu erzeugen:
animation-delay:
calc((var(--i) + var(--m) - var(--j))*#{$t}/(2*var(--m)) - #{$t}),
calc((var(--i) + var(--m) - var(--j))*#{$t}/(2*var(--m)) - #{1.5*$t})
Ein weiteres Beispiel ist das folgende, bei dem die Punkte mit Hilfe von Pseudoelementen erstellt werden:
Da Pseudoelemente zusammen mit ihren Eltern skaliert werden, müssen wir auch die Skalierungstransformation auf ihnen umkehren:
.spike {
/* other spike styles */
transform: var(--position) scalex(var(--fx));
&::before, &::after {
/* other pseudo styles */
transform: scalex(calc(1/var(--fx)));
}
}
Ändern von... clip-path?!
Dies ist eine Methode, die ich sehr mag, auch wenn sie den Support vor Chromium Edge und Internet Explorer ausschließt.
Fast jedes Nutzungsbeispiel von clip-path verwendet entweder einen polygon()-Wert oder einen SVG-Referenzwert. Wenn Sie jedoch einige meiner früheren Artikel gesehen haben, wissen Sie wahrscheinlich, dass es andere Grundformen gibt, die wir verwenden können, wie inset(), das wie unten gezeigt funktioniert:
inset()-Funktion. (Demo)Um den CodePen-Effekt mit dieser Methode zu reproduzieren, setzen wir die ::after-Offsets auf die erweiterten Zustandswerte und *schneiden* dann mit Hilfe von clip-path aus, was wir nicht sehen wollen.
.single-item::after {
top: -1rem;
right: -1rem;
bottom: -3em;
left: -1em;
clip-path: inset(2rem 0 2rem 2rem)
}
Und dann, im :hover-Zustand, setzen wir alle Insets auf Null:
.single-item:hover::after {
clip-path: inset(0)
}
Das kann man unten in Aktion sehen:
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Okay, das funktioniert, aber wir brauchen auch eine Eckabrundung. Glücklicherweise erlaubt uns inset(), auch das anzugeben, als jeden gewünschten border-radius-Wert.
Hier reicht ein Wert von 10px für alle Ecken in beiden Richtungen.
.single-item::after {
/* same styles as before */
clip-path: inset(2rem 0 2rem 2rem round 10px)
}
.single-item:hover::after {
clip-path: inset(0 round 10px)
}
Und das gibt uns genau das, was wir wollten:
Siehe den Pen von thebabydino (@thebabydino) auf CodePen.
Außerdem bricht es in Browsern, die dies nicht unterstützen, nichts, es bleibt einfach immer im erweiterten Zustand.
Diese Methode eignet sich zwar hervorragend für viele Situationen – einschließlich des CodePen-Anwendungsfalls –, aber sie funktioniert nicht, wenn unsere sich erweiternden/schrumpfenden Elemente Nachfahren haben, die außerhalb der border-box ihres geclippten Elternteils liegen, wie im letzten Beispiel mit der zuvor diskutierten Skalierungsmethode.
In Ihrem Abschnitt Ändern der Skalierung sagen Sie: "...calc() ist auch keine Lösung, da es nicht erlaubt, einen Längenwert durch einen anderen Längenwert zu teilen, um einen einheitenlosen Skalierungsfaktor zu erhalten."
Ich bin mir nicht sicher, warum wir so etwas nicht tun können:
Es scheint gut zu funktionieren: Codebeispiel
Fehlt mir etwas?
Der Teil, den Sie zitiert haben, besagt:
In Ihrem Beispiel verwenden Sie gegebene Skalierungsfaktoren und Sie teilen durch sie, um den
border-radiuszu erhalten. Was in Ordnung ist, ich habe auch ein Beispiel dafür in der Demo. Aber es hat nichts mit dem zu tun, was ich in diesem Zitat sage. Sie berechnen die Skalierungsfaktoren nicht, indem Sie die Abmessungen im erweiterten Zustand (was die Abmessungen im ursprünglichen Zustand plus die Erweiterungsbeträge sind) durch die Abmessungen im ursprünglichen Zustand teilen.Zum Beispiel, wenn die ursprünglichen Abmessungen
30vw x 50vhsind und Sie die Box um sagen wir1remin jede Richtung erweitern, dann sind die Skalierungsfaktoren(30vw + 2rem)/30vwhorizontal und(50vh + 2rem)/50vhvertikal.Sass kann das nicht tun, da Sie keine feste Beziehung zwischen
remund Viewport-Einheiten haben.calc()kann das auch nicht tun, da es das Teilen eines Längenwerts durch einen anderen Längenwert nicht zulässt, was genau das ist, was ich in diesem Zitat sage.Ana, danke für den detaillierten Artikel und das Teilen so vieler Optionen, aber die Clip-Methode war einfach großartig. :)