Xlera8

En snygg svävningseffekt för din avatar

Känner du till den typen av effekt där någons huvud sticker genom en cirkel eller ett hål? Den berömda Porky Pig-animationen där han vinkar adjö medan han dyker upp ur en serie röda ringar är det perfekta exemplet, och Kilian Valkhof återskapade faktiskt det här på CSS-Tricks för ett tag sedan.

Jag har en liknande idé men tacklade på ett annat sätt och med ett stänk av animation. Jag tycker att det är ganska praktiskt och ger en snygg svävningseffekt som du kan använda på något som din egen avatar.

Se det? Vi kommer att göra en skalande animation där avataren tycks dyka upp direkt ur cirkeln den befinner sig i. Coolt, eller hur? Titta inte på koden och låt oss bygga den här animationen steg för steg.

HTML: Bara ett element

Om du inte har kollat ​​koden för demot och du undrar hur många divDet här tar, sluta då, eftersom vår markering inte är något annat än ett enda bildelement:

<img src="" alt="">

Ja, ett enda element! Den utmanande delen av den här övningen är att använda minsta möjliga mängd kod. Om du har varit det följer mig ett tag bör du vara van vid detta. Jag försöker hårt för att hitta CSS-lösningar som kan uppnås med den minsta, mest underhållbara koden som möjligt.

Jag skrev en serie artiklar här på CSS-Tricks där jag utforskar olika hovringseffekter med samma HTML-uppmärkning som innehåller ett enda element. Jag går in i detalj på gradienter, maskering, klippning, konturer och till och med layouttekniker. Jag rekommenderar starkt att kolla in dem eftersom jag kommer att återanvända många av tricken i det här inlägget.

En bildfil som är fyrkantig med en genomskinlig bakgrund fungerar bäst för det vi gör. Här är den jag använder om du vill börja med den.

Designad av cang

Jag hoppas kunna se massor av exempel på detta som möjligt med riktiga bilder - så dela gärna ditt slutresultat i kommentarerna när du är klar så att vi kan bygga en samling!

Innan vi hoppar in i CSS, låt oss först dissekera effekten. Bilden blir större när den svävar, så vi kommer säkert att använda den transform: scale() där inne. Det finns en cirkel bakom avataren, och en radiell gradient borde göra susen. Slutligen behöver vi ett sätt att skapa en kant längst ner i cirkeln som skapar utseendet på avataren bakom cirkeln.

Låt oss börja arbeta!

Skaleffekten

Låt oss börja med att lägga till transformationen:

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

Inget komplicerat ännu, eller hur? Låt oss gå vidare.

Cirkeln

Vi sa att bakgrunden skulle vara en radiell gradient. Det är perfekt eftersom vi kan skapa hårda stopp mellan färgerna i en radiell gradient, vilket får det att se ut som om vi ritar en cirkel med heldragna linjer.

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);
}

Observera CSS-variabeln, --b, jag använder där. Det representerar tjockleken på "kanten" som egentligen bara används för att definiera de hårda färgstoppen för den röda delen av den radiella gradienten.

Nästa steg är att leka med gradientstorleken när du svävar. Cirkeln måste behålla sin storlek när bilden växer. Eftersom vi tillämpar en scale() transformation behöver vi faktiskt minskning storleken på cirkeln eftersom den annars skalar upp med avataren. Så medan bilden skalas upp behöver vi gradienten för att skala ner.

Låt oss börja med att definiera en CSS-variabel, --f, som definierar "skalfaktorn", och använd den för att ställa in storleken på cirkeln. Jag använder 1 som standardvärde, eftersom det är den initiala skalan för bilden och cirkeln som vi transformerar från.

Här är en demo för att illustrera tricket. Håll muspekaren för att se vad som händer bakom kulisserna:

Jag lade till en tredje färg till radial-gradient för att bättre identifiera området för gradienten vid hovring:

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

Nu måste vi placera vår bakgrund i mitten av cirkeln och se till att den tar upp hela höjden. Jag gillar att deklarera allt direkt på background stenografiegenskap, så att vi kan lägga till vår bakgrundspositionering och se till att den inte upprepas genom att slå på dessa värden direkt efter radial-gradient():

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

Bakgrunden är placerad i mitten (50%), har en bredd lika med calc(100%/var(--f)), och har en höjd lika med 100%.

Inget skalar när --f är lika med 1 — återigen, vår första skala. Under tiden tar gradienten upp behållarens fulla bredd. När vi ökar --f, elementets storlek växer — tack vare scale() transformera — och gradientens storlek minskar.

Det här är vad vi får när vi tillämpar allt detta på vår demo:

Vi närmar oss! Vi har översvämningseffekten överst, men vi behöver fortfarande dölja den nedre delen av bilden, så det ser ut som att den hoppar ut ur cirkeln istället för att sitta framför den. Det är den knepiga delen av det hela och det är vad vi ska göra härnäst.

Den nedre kanten

Jag försökte först ta itu med detta med border-bottom egenskap, men jag kunde inte hitta ett sätt att matcha storleken på kanten till storleken på cirkeln. Här är det bästa jag kunde få och du kan direkt se att det är fel:

Den faktiska lösningen är att använda outline fast egendom. Ja, outline, Inte border. I en tidigare artikel, jag visar hur outline är kraftfull och låter oss skapa coola svävningseffekter. Kombinerad med outline-offset, vi har precis vad vi behöver för vår effekt.

Tanken är att sätta en outline på bilden och justera dess förskjutning för att skapa den nedre kanten. Offset kommer att bero på skalningsfaktorn på samma sätt som gradientstorleken gjorde.

Nu har vi vår nedre "gräns" (faktiskt en outline) kombinerat med "kanten" som skapas av gradienten för att skapa en hel cirkel. Vi behöver fortfarande gömma delar av outline (från toppen och sidorna), vilket vi kommer till om ett ögonblick.

Här är vår kod hittills, inklusive ett par fler CSS-variabler som du kan använda för att konfigurera bildstorleken (--s) och "kant"-färgen (--c):

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 */
}

Eftersom vi behöver en cirkulär bottenkant lade vi till en border-radius på undersidan, vilket gör att outline för att matcha gradientens krökning.

Beräkningen som används på outline-offset är mycket enklare än det ser ut. Som standard, outline ritas utanför av elementets låda. Och i vårt fall behöver vi det överlappning elementet. Mer exakt, vi behöver det för att följa cirkeln som skapas av gradienten.

Diagram över bakgrundsövergången.

När vi skalar elementet ser vi mellanrummet mellan cirkeln och kanten. Låt oss inte glömma att tanken är att behålla cirkeln i samma storlek efter att skalomvandlingen löper, vilket lämnar oss med det utrymme vi kommer att använda för att definiera konturens offset som illustreras i figuren ovan.

Låt oss inte glömma att det andra elementet är skalat, så vårt resultat är också skalat... vilket betyder att vi måste dividera resultatet med f för att få det verkliga offsetvärdet:

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

Vi lägger till ett negativt tecken eftersom vi behöver konturen för att gå från utsidan till insidan:

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

Här är en snabb demo som visar hur konturen följer gradienten:

Du kanske redan ser det, men vi behöver fortfarande den nedre konturen för att överlappa cirkeln istället för att låta den blöda igenom den. Vi kan göra det genom att ta bort gränsens storlek från offset:

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

Nu måste vi hitta hur man tar bort den övre delen från konturen. Med andra ord vill vi bara ha den nedre delen av bildens outline.

Låt oss först lägga till utrymme längst upp med stoppning för att undvika överlappning i toppen:

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 */
}

Det finns ingen speciell logik i den övre stoppningen. Tanken är att se till att konturen inte rör avatarens huvud. Jag använde elementets storlek för att definiera det utrymmet för att alltid ha samma proportion.

Observera att jag har lagt till content-box värde till background:

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;

Vi behöver detta eftersom vi har lagt till utfyllnad och vi vill bara ha bakgrunden inställd på innehållsrutan, så vi måste uttryckligen tala om för bakgrunden att stanna där.

Lägger till CSS-mask till mixen

Vi nådde sista delen! Allt vi behöver göra är att gömma några bitar, och vi är klara. För detta kommer vi att förlita oss på mask egendom och, naturligtvis, gradienter.

Här är en figur för att illustrera vad vi behöver dölja eller vad vi behöver visa för att vara mer exakt

Visar hur masken appliceras på den nedre delen av cirkeln.

Den vänstra bilden är vad vi har för närvarande, och den högra är vad vi vill ha. Den gröna delen illustrerar masken vi måste applicera på originalbilden för att få det slutliga resultatet.

Vi kan identifiera två delar av vår mask:

  • En cirkulär del längst ner som har samma dimension och krökning som den radiella gradienten vi använde för att skapa cirkeln bakom avataren
  • En rektangel i toppen som täcker området innanför konturen. Lägg märke till hur konturen är utanför det gröna området längst upp - det är den viktigaste delen, eftersom det gör att konturen kan skäras så att endast den nedre delen är synlig.

Här är vår sista CSS:

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 */
}

Låt oss bryta ner det mask fast egendom. Till att börja med, märk att en liknande radial-gradient() från background egendom finns där. Jag skapade en ny variabel, --_g, för att de gemensamma delarna ska göra saker mindre röriga.

--_g: 50% / calc(100% / var(--f)) 100% no-repeat content-box; mask: radial-gradient( circle closest-side, #000 99%, #0000) var(--_g);

Därefter finns det en linear-gradient() där också:

--_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);

Detta skapar rektangeldelen av masken. Dess bredd är lika med den radiella gradientens bredd minus två gånger kanttjockleken:

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

Rektangelns höjd är lika med hälften, 50%, av elementets storlek.

Vi behöver också den linjära gradienten placerad i det horisontella mitten (50%) och förskjuten från toppen med samma värde som konturens offset. Jag skapade en annan CSS-variabel, --_o, för offset vi tidigare definierat:

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

En av de förvirrande sakerna här är att vi behöver en negativ offset för konturen (för att flytta den från utsidan till insidan) men a positiv offset för gradienten (för att flytta från topp till botten). Så om du undrar varför vi multiplicerar offseten, --_o, av -1, nu vet du det!

Här är en demo för att illustrera maskens gradientkonfiguration:

Håll muspekaren ovan och se hur allt rör sig tillsammans. Den mittersta rutan illustrerar maskskiktet som består av två gradienter. Föreställ dig det som den synliga delen av den vänstra bilden, så får du det slutliga resultatet till höger!

Inslagning upp

Oj, vi är klara! Och inte bara slutade vi med en snygg svävningsanimering, utan vi gjorde allt med en enda HTML <img> element. Bara det och mindre än 20 rader med CSS-trick!

Visst, vi förlitade oss på några små knep och matematiska formler för att nå en så komplex effekt. Men vi visste exakt vad vi skulle göra eftersom vi identifierade de bitar vi behövde på förhand.

Kunde vi ha förenklat CSS om vi tillåtit oss mer HTML? Absolut. Men vi är här för att lära oss nya CSS-trick! Detta var en bra övning för att utforska CSS-gradienter, maskering, outline fastighetens beteende, transformationer och en hel massa mer. Om du kände dig vilsen vid något tillfälle, kolla definitivt in min serie som använder samma allmänna begrepp. Ibland hjälper det att se fler exempel och använda fall för att få en poäng hem.

Jag lämnar dig med en sista demo som använder bilder av populära CSS-utvecklare. Glöm inte att visa mig en demo med din egen bild så att jag kan lägga till den i samlingen!

Chatta med oss

Hallå där! Hur kan jag hjälpa dig?