Xlera8

En fancy sveveeffekt for avataren din

Kjenner du den typen effekt der noens hode stikker gjennom en sirkel eller et hull? Den berømte Porky Pig-animasjonen der han vinker farvel mens han spretter ut av en rekke røde ringer er det perfekte eksempelet, og Kilian Valkhof gjenskapte faktisk det her på CSS-Tricks for en stund tilbake.

Jeg har en lignende idé, men taklet på en annen måte og med et dryss animasjon. Jeg synes det er ganske praktisk og gir en pen sveveeffekt du kan bruke på noe som din egen avatar.

Se det? Vi skal lage en skaleringsanimasjon der avataren ser ut til å sprette rett ut av sirkelen den er i. Kult, ikke sant? Ikke se på koden, og la oss bygge denne animasjonen sammen trinn for trinn.

HTML: Bare ett element

Hvis du ikke har sjekket koden til demoen og du lurer på hvor mange divSå lenge dette tar, så stopp der, fordi markeringen vår ikke er annet enn et enkelt bildeelement:

<img src="" alt="">

Ja, et enkelt element! Den utfordrende delen av denne øvelsen er å bruke minst mulig kode. Hvis du har vært det følger meg for en stund, bør du være vant til dette. Jeg prøver hardt å finne CSS-løsninger som kan oppnås med den minste, mest vedlikeholdbare koden mulig.

Jeg skrev en serie artikler her på CSS-Tricks hvor jeg utforsker forskjellige sveveeffekter ved å bruke den samme HTML-markeringen som inneholder et enkelt element. Jeg går i detalj på gradienter, maskering, klipping, konturer og til og med layoutteknikker. Jeg anbefaler på det sterkeste å sjekke disse fordi jeg vil gjenbruke mange av triksene i dette innlegget.

En bildefil som er firkantet med en gjennomsiktig bakgrunn vil fungere best for det vi gjør. Her er den jeg bruker hvis du vil begynne med den.

Designet av cang

Jeg håper å se mange eksempler på dette som mulig ved bruk av ekte bilder - så del endelig resultatet i kommentarfeltet når du er ferdig, slik at vi kan bygge en samling!

Før vi hopper inn i CSS, la oss først dissekere effekten. Bildet blir større ved sveving, så vi kommer garantert til å bruke det transform: scale() der inne. Det er en sirkel bak avataren, og en radiell gradient bør gjøre susen. Til slutt trenger vi en måte å lage en kant på nederst i sirkelen som skaper utseendet til avataren bak sirkelen.

La oss komme på jobb!

Skalaeffekten

La oss starte med å legge til transformasjonen:

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

Ikke noe komplisert ennå, ikke sant? La oss gå videre.

Sirkelen

Vi sa at bakgrunnen ville være en radiell gradient. Det er perfekt fordi vi kan lage harde stopp mellom fargene til en radiell gradient, som får det til å se ut som om vi tegner en sirkel med heltrukne 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);
}

Legg merke til CSS-variabelen, --b, jeg bruker der. Den representerer tykkelsen på "kanten" som egentlig bare brukes til å definere de harde fargestoppene for den røde delen av den radielle gradienten.

Neste trinn er å leke med gradientstørrelsen når du svever. Sirkelen må beholde størrelsen når bildet vokser. Siden vi søker en scale() transformasjon, trenger vi faktisk redusere størrelsen på sirkelen fordi den ellers skaleres opp med avataren. Så mens bildet skaleres opp, trenger vi gradienten for å skalere ned.

La oss starte med å definere en CSS-variabel, --f, som definerer "skalafaktoren", og bruk den til å angi størrelsen på sirkelen. jeg bruker 1 som standardverdi, som i det er den første skalaen for bildet og sirkelen vi transformerer fra.

Her er en demo for å illustrere trikset. Hold musepekeren for å se hva som skjer bak kulissene:

Jeg la til en tredje farge radial-gradient for bedre å identifisere området for gradienten ved sveving:

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

Nå må vi plassere bakgrunnen vår i midten av sirkelen og sørge for at den tar opp hele høyden. Jeg liker å deklarere alt direkte på background stenografi, slik at vi kan legge til bakgrunnsposisjonen vår og sørge for at den ikke gjentar seg ved å slå på disse verdiene rett etter radial-gradient():

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

Bakgrunnen er plassert i midten (50%), har en bredde lik calc(100%/var(--f)), og har en høyde lik 100%.

Ingenting skalerer når --f er lik 1 – igjen, vår første skala. I mellomtiden tar gradienten opp hele bredden av beholderen. Når vi øker --f, elementets størrelse vokser - takket være scale() transformere — og gradientens størrelse reduseres.

Her er hva vi får når vi bruker alt dette på demoen vår:

Vi nærmer oss! Vi har overløpseffekten øverst, men vi må fortsatt skjule den nederste delen av bildet, så det ser ut som det spretter ut av sirkelen i stedet for å sitte foran det. Det er den vanskelige delen av hele denne greia, og det er det vi skal gjøre videre.

Den nederste kantlinjen

Jeg prøvde først å takle dette med border-bottom egenskap, men jeg klarte ikke å finne en måte å matche størrelsen på kantlinjen til størrelsen til sirkelen. Her er det beste jeg kan få, og du kan umiddelbart se at det er feil:

Den faktiske løsningen er å bruke outline eiendom. Ja, outline, Ikke border. i en tidligere artikkel, jeg viser hvordan outline er kraftig og lar oss lage kule sveveeffekter. Kombinert med outline-offset, vi har akkurat det vi trenger for vår effekt.

Tanken er å sette en outline på bildet og juster forskyvningen for å lage den nederste rammen. Forskyvningen vil avhenge av skaleringsfaktoren på samme måte som gradientstørrelsen gjorde.

Nå har vi vår nederste "grense" (faktisk en outline) kombinert med "kanten" opprettet av gradienten for å lage en hel sirkel. Vi må fortsatt skjule deler av outline (fra toppen og sidene), som vi kommer til om et øyeblikk.

Her er koden vår så langt, inkludert et par flere CSS-variabler du kan bruke til å konfigurere bildestørrelsen (--s) og "kantfargen" (--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 */
}

Siden vi trenger en sirkulær bunnkant, la vi til en border-radius på undersiden, slik at outline for å matche krumningen til gradienten.

Regnestykket brukt på outline-offset er mye mer enkelt enn det ser ut. Som standard, outline er tegnet utenfor av elementets boks. Og i vårt tilfelle trenger vi det overlapping elementet. Mer presist, vi trenger den for å følge sirkelen skapt av gradienten.

Diagram over bakgrunnsovergangen.

Når vi skalerer elementet, ser vi mellomrommet mellom sirkelen og kanten. La oss ikke glemme at ideen er å holde sirkelen i samme størrelse etter at skalatransformasjonen går, noe som etterlater oss med plassen vi vil bruke til å definere konturens offset som illustrert i figuren ovenfor.

La oss ikke glemme at det andre elementet er skalert, så resultatet vårt er også skalert ... som betyr at vi må dele resultatet med f for å få den virkelige offsetverdien:

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

Vi legger til et negativt tegn siden vi trenger at omrisset går fra utsiden til innsiden:

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

Her er en rask demo som viser hvordan omrisset følger gradienten:

Det kan hende du allerede ser det, men vi trenger fortsatt den nederste omrisset for å overlappe sirkelen i stedet for å la den blø gjennom den. Vi kan gjøre det ved å fjerne grensens størrelse fra forskyvningen:

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

Nå må vi finne hvordan vi fjerner den øvre delen fra omrisset. Med andre ord, vi vil bare ha den nederste delen av bildets outline.

La oss først legge til plass på toppen med polstring for å unngå overlapping på 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 er ingen spesiell logikk i den topppolstringen. Tanken er å sikre at omrisset ikke berører hodet til avataren. Jeg brukte elementets størrelse for å definere den plassen til alltid å ha samme proporsjon.

Merk at jeg har lagt til content-box verdi til 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 trenger dette fordi vi la til polstring og vi vil bare ha bakgrunnen satt til innholdsboksen, så vi må eksplisitt fortelle bakgrunnen om å stoppe der.

Legger til CSS-maske til blandingen

Vi nådde siste delen! Alt vi trenger å gjøre er å skjule noen biter, og vi er ferdige. For dette vil vi stole på mask eiendom og selvfølgelig gradienter.

Her er en figur for å illustrere hva vi trenger å skjule eller hva vi må vise for å være mer nøyaktig

Viser hvordan masken gjelder den nederste delen av sirkelen.

Det venstre bildet er det vi har for øyeblikket, og det høyre er det vi ønsker. Den grønne delen illustrerer masken vi må bruke på originalbildet for å få det endelige resultatet.

Vi kan identifisere to deler av masken vår:

  • En sirkulær del nederst som har samme dimensjon og krumning som den radielle gradienten vi brukte for å lage sirkelen bak avataren
  • Et rektangel på toppen som dekker området innenfor omrisset. Legg merke til hvordan omrisset er utenfor det grønne området øverst - det er den viktigste delen, siden den lar omrisset kuttes slik at bare den nederste delen er synlig.

Her er vår siste 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 */
}

La oss bryte ned det mask eiendom. For det første, legg merke til at en lignende radial-gradient() fra background eiendom er der inne. Jeg opprettet en ny variabel, --_g, for at fellesdelene skal gjøre ting mindre rotete.

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

Deretter er det en linear-gradient() der også:

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

Dette skaper rektangeldelen av masken. Dens bredde er lik den radielle gradientens bredde minus to ganger kanttykkelsen:

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

Rektangelets høyde er lik halvparten, 50%, av elementets størrelse.

Vi trenger også den lineære gradienten plassert i det horisontale sentrum (50%) og forskyv fra toppen med samme verdi som disposisjonens offset. Jeg opprettet en annen CSS-variabel, --_o, for forskyvningen vi tidligere definerte:

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

En av de forvirrende tingene her er at vi trenger en negativ forskyvning for omrisset (for å flytte den fra utsiden til innsiden), men en positiv offset for gradienten (for å flytte fra topp til bunn). Så hvis du lurer på hvorfor vi multipliserer forskyvningen, --_o, Ved -1, vel, nå vet du det!

Her er en demo for å illustrere maskens gradientkonfigurasjon:

Hold musepekeren ovenfor og se hvordan alt henger sammen. Den midterste boksen illustrerer maskelaget som består av to gradienter. Se for deg det som den synlige delen av det venstre bildet, og du får det endelige resultatet til høyre!

Innpakning opp

Uff, vi er ferdige! Og ikke bare endte vi opp med en glatt sveveanimasjon, men vi gjorde alt med en enkelt HTML <img> element. Bare det og mindre enn 20 linjer med CSS-lureri!

Jada, vi stolte på noen små triks og matematiske formler for å oppnå en så kompleks effekt. Men vi visste nøyaktig hva vi skulle gjøre siden vi identifiserte brikkene vi trengte på forhånd.

Kunne vi ha forenklet CSS hvis vi tillot oss mer HTML? Absolutt. Men vi er her for å lære nye CSS-triks! Dette var en god øvelse for å utforske CSS-gradienter, maskering, outline eiendommens oppførsel, transformasjoner og en hel haug mer. Hvis du følte deg fortapt på noe tidspunkt, sjekk definitivt ut serien min som bruker de samme generelle konseptene. Noen ganger hjelper det å se flere eksempler og bruke saker for å kjøre et poeng hjem.

Jeg vil gi deg en siste demo som bruker bilder av populære CSS-utviklere. Ikke glem å vise meg en demo med ditt eget bilde, slik at jeg kan legge det til samlingen!

Chat med oss

Hei der! Hvordan kan jeg hjelpe deg?