Ein schicker Hover-Effekt für deinen Avatar

Avatar of Temani Afif
Temani Afif am

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

Kennst du diesen Effekt, bei dem der Kopf einer Person durch einen Kreis oder ein Loch lugt? Die berühmte Porky Pig-Animation, bei der er winkt, während er aus einer Reihe roter Ringe auftaucht, ist das perfekte Beispiel, und Kilian Valkhof hat das hier auf CSS-Tricks schon vor einiger Zeit nachgebildet.

Ich habe eine ähnliche Idee, aber auf eine andere Weise angegangen und mit einer Prise Animation. Ich finde, sie ist ziemlich praktisch und ergibt einen schönen Hover-Effekt, den du zum Beispiel für deinen eigenen Avatar verwenden kannst.

Siehst du das? Wir werden eine Skalierungsanimation erstellen, bei der der Avatar aus dem Kreis, in dem er sich befindet, herausspringt. Cool, oder? Schau nicht auf den Code und lass uns diese Animation Schritt für Schritt gemeinsam erstellen.

Der HTML-Code: Nur ein Element

Wenn du dir den Code der Demo noch nicht angesehen hast und dich fragst, wie viele divs das wohl brauchen wird, dann hör sofort auf, denn unser Markup besteht aus nichts weiter als einem einzigen Bild-Element.

<img src="" alt="">

Ja, ein einziges Element! Der herausfordernde Teil dieser Übung ist die Verwendung der kleinstmöglichen Menge an Code. Wenn du mir schon eine Weile folgst, solltest du dich daran gewöhnt haben. Ich bemühe mich sehr, CSS-Lösungen zu finden, die mit dem kleinsten und am besten wartbaren Code möglich sind.

Ich habe eine Reihe von Artikeln hier auf CSS-Tricks geschrieben, in denen ich verschiedene Hover-Effekte mit demselben HTML-Markup, das ein einzelnes Element enthält, untersuche. Ich gehe detailliert auf Farbverläufe, Maskierung, Clipping, Outlines und sogar Layout-Techniken ein. Ich empfehle dir dringend, diese zu lesen, da ich viele der Tricks in diesem Beitrag wiederverwenden werde.

Eine Bilddatei, die quadratisch ist und einen transparenten Hintergrund hat, eignet sich am besten für das, was wir vorhaben. Hier ist das, das ich verwende, falls du damit beginnen möchtest.

Gestaltet von Cang

Ich hoffe, viele Beispiele dafür zu sehen! – also teile bitte dein Endergebnis in den Kommentaren, wenn du fertig bist, damit wir eine Sammlung aufbauen können!

Bevor wir uns dem CSS widmen, wollen wir den Effekt zunächst analysieren. Das Bild wird beim Hover größer, also werden wir auf jeden Fall transform: scale() darin verwenden. Dahinter dem Avatar befindet sich ein Kreis, und ein radialer Farbverlauf sollte den Trick machen. Schließlich brauchen wir eine Möglichkeit, einen Rand am unteren Rand des Kreises zu erzeugen, der den Anschein erweckt, dass sich der Avatar hinter dem Kreis befindet.

Legen wir los!

Der Skalierungseffekt

Beginnen wir mit dem Hinzufügen der Transformation

img {
  width: 280px;
  aspect-ratio: 1;
  cursor: pointer;
  transition: .5s;
}
img:hover {
  transform: scale(1.35);
}

Noch nichts Kompliziertes, oder? Machen wir weiter.

Der Kreis

Wir sagten, dass der Hintergrund ein radialer Farbverlauf sein würde. Das ist perfekt, denn wir können harte Stopps zwischen den Farben eines radialen Farbverlaufs erzeugen, wodurch es so aussieht, als würden wir einen Kreis mit soliden Linien zeichnen.

img {
  --b: 5px; /* border width */

  width: 280px;
  aspect-ratio: 1;
  background:
    radial-gradient(
      circle closest-side,
      #ECD078 calc(99% - var(--b)),
      #C02942 calc(100% - var(--b)) 99%,
      #0000
    );
  cursor: pointer;
  transition: .5s;
}
img:hover {
  transform: scale(1.35);
}

Beachte die CSS-Variable --b, die ich dort verwende. Sie repräsentiert die Dicke des „Randes“, die eigentlich nur zur Definition der harten Farbstopps für den roten Teil des radialen Farbverlaufs verwendet wird.

Der nächste Schritt ist das Spielen mit der Gradientengröße beim Hover. Der Kreis muss seine Größe beibehalten, während das Bild wächst. Da wir eine scale()-Transformation anwenden, müssen wir die Größe des Kreises tatsächlich *verkleinern*, da er sich sonst zusammen mit dem Avatar hochskaliert. Wenn sich also das Bild hochskaliert, müssen wir den Gradienten kleiner skalieren.

Beginnen wir mit der Definition einer CSS-Variable --f, die den „Skalierungsfaktor“ definiert und zur Einstellung der Größe des Kreises verwendet wird. Ich verwende 1 als Standardwert, da dies die anfängliche Skalierung für das Bild und den Kreis ist, von dem wir transformieren.

Hier ist eine Demo, die den Trick veranschaulicht. Fahre mit der Maus darüber, um zu sehen, was im Hintergrund passiert.

Ich habe dem radial-gradient eine dritte Farbe hinzugefügt, um den Bereich des Gradienten beim Hover besser zu identifizieren.

radial-gradient(
  circle closest-side,
  #ECD078 calc(99% - var(--b)),
  #C02942 calc(100% - var(--b)) 99%,
  lightblue
);

Nun müssen wir unseren Hintergrund in der Mitte des Kreises positionieren und sicherstellen, dass er die gesamte Höhe einnimmt. Ich deklariere gerne alles direkt auf der background-Kurzschrifteigenschaft, sodass wir unsere Hintergrundpositionierung hinzufügen und sicherstellen können, dass sie sich nicht wiederholt, indem wir diese Werte direkt nach der radial-gradient() anhängen.

background: radial-gradient() 50% / calc(100% / var(--f)) 100% no-repeat;

Der Hintergrund ist in der Mitte positioniert (50%), hat eine Breite, die calc(100%/var(--f)) entspricht, und eine Höhe, die 100% entspricht.

Nichts skaliert, wenn --f gleich 1 ist – wieder unsere anfängliche Skalierung. Währenddessen nimmt der Gradient die volle Breite des Containers ein. Wenn wir --f erhöhen, wächst die Elementgröße – dank der scale()-Transformation – und die Gradientengröße nimmt ab.

Hier sehen wir, was passiert, wenn wir all das auf unsere Demo anwenden.

Wir kommen näher! Wir haben den Überlaufeffekt oben, aber wir müssen immer noch den unteren Teil des Bildes ausblenden, damit es aussieht, als würde es aus dem Kreis herausspringen, anstatt davor zu sitzen. Das ist der knifflige Teil des Ganzen und das werden wir als nächstes tun.

Der untere Rand

Ich habe zuerst versucht, dies mit der border-bottom-Eigenschaft zu lösen, aber ich konnte keine Möglichkeit finden, die Größe des Randes an die Größe des Kreises anzupassen. Hier ist das Beste, was ich erreichen konnte, und du siehst sofort, dass es falsch ist.

Die eigentliche Lösung ist die Verwendung der outline-Eigenschaft. Ja, outline, nicht border. In einem früheren Artikel zeige ich, wie mächtig outline ist und uns coole Hover-Effekte ermöglicht. In Kombination mit outline-offset haben wir genau das, was wir für unseren Effekt brauchen.

Die Idee ist, eine outline am Bild festzulegen und ihren Abstand anzupassen, um den unteren Rand zu erzeugen. Der Abstand hängt vom Skalierungsfaktor ab, genau wie die Gradientengröße.

Jetzt haben wir unseren unteren „Rand“ (eigentlich eine outline), der mit dem durch den Gradienten erzeugten „Rand“ kombiniert wird, um einen vollständigen Kreis zu bilden. Wir müssen immer noch Teile der outline ausblenden (oben und an den Seiten), was wir gleich tun werden.

Hier ist unser bisheriger Code, einschließlich einiger weiterer CSS-Variablen, die du zur Konfiguration der Bildgröße (--s) und der „Rand“-Farbe (--c) verwenden kannst.

img {
  --s: 280px; /* image size */
  --b: 5px; /* border thickness */
  --c: #C02942; /* border color */
  --f: 1; /* initial scale */

  width: var(--s);
  aspect-ratio: 1;
  cursor: pointer;
  border-radius: 0 0 999px 999px;
  outline: var(--b) solid var(--c);
  outline-offset: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b));
  background: 
    radial-gradient(
      circle closest-side,
      #ECD078 calc(99% - var(--b)),
      var(--c) calc(100% - var(--b)) 99%,
      #0000
    ) 50% / calc(100% / var(--f)) 100% no-repeat;
  transform: scale(var(--f));
  transition: .5s;
}
img:hover {
  --f: 1.35; /* hover scale */
}

Da wir einen runden unteren Rand benötigen, haben wir an der Unterseite eine border-radius hinzugefügt, damit die outline der Krümmung des Gradienten entspricht.

Die Berechnung, die bei outline-offset verwendet wird, ist weitaus unkomplizierter, als sie aussieht. Standardmäßig wird die outline *außerhalb* der Box des Elements gezeichnet. Und in unserem Fall muss sie das Element *überlappen*. Genauer gesagt, sie muss dem Kreis folgen, der durch den Gradienten erzeugt wird.

Diagram of the background transition.

Wenn wir das Element skalieren, sehen wir den Abstand zwischen dem Kreis und dem Rand. Vergessen wir nicht, dass die Idee darin besteht, den Kreis nach der Skalierungstransformation auf gleicher Größe zu halten, was uns den Abstand hinterlässt, den wir zur Definition des Abstands der Outline verwenden, wie in der obigen Abbildung dargestellt.

Vergessen wir nicht, dass das zweite Element skaliert wird, also wird auch unser Ergebnis skaliert… was bedeutet, dass wir das Ergebnis durch f dividieren müssen, um den tatsächlichen Offset-Wert zu erhalten.

Offset = ((f - 1) * S/2) / f = (1 - 1/f) * S/2

Wir fügen ein Minuszeichen hinzu, da wir möchten, dass die Outline von außen nach innen verläuft.

Offset = (1/f - 1) * S/2

Hier ist eine kurze Demo, die zeigt, wie die Outline dem Gradienten folgt.

Du siehst es vielleicht schon, aber wir müssen immer noch dafür sorgen, dass die untere Outline den Kreis überlappt, anstatt ihn durchscheinen zu lassen. Das können wir erreichen, indem wir die Randdicke vom Offset abziehen.

outline-offset: calc((1 / var(--f) - 1) * var(--s) / 2) - var(--b));

Nun müssen wir herausfinden, wie wir den oberen Teil der Outline entfernen. Mit anderen Worten, wir wollen nur den unteren Teil der outline des Bildes.

Zuerst fügen wir oben mit Padding etwas Abstand hinzu, um die Überlappung oben zu vermeiden.

img {
  --s: 280px; /* image size */
  --b: 5px;   /* border thickness */
  --c: #C02942; /* border color */
  --f: 1; /* initial scale */

  width: var(--s);
  aspect-ratio: 1;
  padding-block-start: calc(var(--s)/5);
  /* etc. */
}
img:hover {
  --f: 1.35; /* hover scale */
}

Es gibt keine besondere Logik für dieses obere Padding. Die Idee ist, sicherzustellen, dass die Outline nicht den Kopf des Avatars berührt. Ich habe die Elementgröße verwendet, um diesen Abstand zu definieren, damit er immer die gleiche Proportion hat.

Beachte, dass ich den Wert content-box zum background hinzugefügt habe.

background:
  radial-gradient(
    circle closest-side,
    #ECD078 calc(99% - var(--b)),
    var(--c) calc(100% - var(--b)) 99%,
    #0000
  ) 50%/calc(100%/var(--f)) 100% no-repeat content-box;

Das brauchen wir, weil wir Padding hinzugefügt haben und der Hintergrund nur auf die Content-Box gesetzt werden soll. Wir müssen dem Hintergrund also explizit sagen, dass er dort aufhören soll.

CSS-Maske hinzufügen

Wir sind beim letzten Teil angekommen! Alles, was wir noch tun müssen, ist, einige Teile auszublenden, und wir sind fertig. Dafür werden wir uns auf die mask-Eigenschaft und natürlich auf Farbverläufe verlassen.

Hier ist eine Abbildung, die veranschaulicht, was wir ausblenden müssen oder, genauer gesagt, was wir zeigen müssen.

Showing how the mask applies to the bottom portion of the circle.

Das linke Bild zeigt, was wir derzeit haben, und das rechte, was wir wollen. Der grüne Teil veranschaulicht die Maske, die wir auf das Originalbild anwenden müssen, um das Endergebnis zu erzielen.

Wir können zwei Teile unserer Maske identifizieren:

  • Einen kreisförmigen Teil unten, der die gleichen Abmessungen und die gleiche Krümmung hat wie der radiale Farbverlauf, den wir zur Erzeugung des Kreises hinter dem Avatar verwendet haben.
  • Ein Rechteck oben, das den Bereich innerhalb des Umrisses abdeckt. Beachte, wie der Umriss oberhalb des grünen Bereichs liegt – das ist der wichtigste Teil, da er es ermöglicht, den Umriss zu beschneiden, sodass nur der untere Teil sichtbar ist.

Hier ist unser endgültiger CSS-Code.

img {
  --s: 280px; /* image size */
  --b: 5px; /* border thickness */
  --c: #C02942; /* border color */
  --f: 1; /* initial scale */

  --_g: 50% / calc(100% / var(--f)) 100% no-repeat content-box;
  --_o: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b));

  width: var(--s);
  aspect-ratio: 1;
  padding-top: calc(var(--s)/5);
  cursor: pointer;
  border-radius: 0 0 999px 999px;
  outline: var(--b) solid var(--c);
  outline-offset: var(--_o);
  background: 
    radial-gradient(
      circle closest-side,
      #ECD078 calc(99% - var(--b)),
      var(--c) calc(100% - var(--b)) 99%,
      #0000) var(--_g);
  mask:
    linear-gradient(#000 0 0) no-repeat
    50% calc(-1 * var(--_o)) / calc(100% / var(--f) - 2 * var(--b)) 50%,
    radial-gradient(
      circle closest-side,
      #000 99%,
      #0000) var(--_g);
  transform: scale(var(--f));
  transition: .5s;
}
img:hover {
  --f: 1.35; /* hover scale */
}

Lassen wir uns diese mask-Eigenschaft aufschlüsseln. Zuerst fällt auf, dass ein ähnlicher radial-gradient() wie in der background-Eigenschaft enthalten ist. Ich habe eine neue Variable, --_g, für die gemeinsamen Teile erstellt, um die Übersichtlichkeit zu verbessern.

--_g: 50% / calc(100% / var(--f)) 100% no-repeat content-box;

mask:
  radial-gradient(
    circle closest-side,
    #000 99%,
    #0000) var(--_g);

Als Nächstes gibt es auch einen linear-gradient().

--_g: 50% / calc(100% / var(--f)) 100% no-repeat content-box;

mask:
  linear-gradient(#000 0 0) no-repeat
    50% calc(-1 * var(--_o)) / calc(100% / var(--f) - 2 * var(--b)) 50%,
  radial-gradient(
    circle closest-side,
    #000 99%,
    #0000) var(--_g);

Dieser erzeugt den rechteckigen Teil der Maske. Seine Breite entspricht der Breite des radialen Farbverlaufs abzüglich der zweifachen Randdicke.

calc(100% / var(--f) - 2 * var(--b))

Die Höhe des Rechtecks entspricht der Hälfte, also 50%, der Elementgröße.

Wir benötigen auch den linearen Farbverlauf, der horizontal zentriert (50%) positioniert und um den gleichen Wert wie der Abstand der Outline vom oberen Rand verschoben ist. Ich habe eine weitere CSS-Variable, --_o, für den zuvor definierten Abstand erstellt.

--_o: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b));

Eine der verwirrenden Sachen hier ist, dass wir einen *negativen* Offset für die Outline benötigen (um sie von außen nach innen zu bewegen), aber einen *positiven* Offset für den Gradienten (um ihn von oben nach unten zu bewegen). Wenn du dich also fragst, warum wir den Offset --_o mit -1 multiplizieren, nun, jetzt weißt du es!

Hier ist eine Demo, die die Konfiguration des Masken-Gradienten veranschaulicht.

Fahre mit der Maus über das Obige und sieh, wie sich alles zusammen bewegt. Die mittlere Box veranschaulicht die Maskenschicht, die aus zwei Gradienten besteht. Stell dir das als den sichtbaren Teil des linken Bildes vor, und du erhältst das Endergebnis auf der rechten Seite!

Zusammenfassung

Puh, wir sind fertig! Und nicht nur, dass wir eine coole Hover-Animation erhalten haben, wir haben das alles mit einem einzigen HTML-<img>-Element geschafft. Nur das und weniger als 20 Zeilen CSS-Trickery!

Sicher, wir haben uns auf einige kleine Tricks und mathematische Formeln verlassen, um einen so komplexen Effekt zu erzielen. Aber wir wussten genau, was zu tun ist, da wir die benötigten Teile im Voraus identifiziert haben.

Hätten wir das CSS vereinfachen können, wenn wir mehr HTML zugelassen hätten? Absolut. Aber wir sind hier, um neue CSS-Tricks zu lernen! Dies war eine gute Übung, um CSS-Farbverläufe, Maskierung, das Verhalten der outline-Eigenschaft, Transformationen und vieles mehr zu erkunden. Wenn du dich an irgendeinem Punkt verloren gefühlt hast, dann schau dir unbedingt meine Serie an, die dieselben allgemeinen Konzepte verwendet. Manchmal hilft es, mehr Beispiele und Anwendungsfälle zu sehen, um einen Punkt zu verdeutlichen.

Ich lasse dich mit einer letzten Demo zurück, die Fotos beliebter CSS-Entwickler verwendet. Vergiss nicht, mir eine Demo mit deinem eigenen Bild zu zeigen, damit ich sie zur Sammlung hinzufügen kann!