Xlera8

En fancy svæveeffekt til din avatar

Kender du den slags effekt, hvor en persons hoved stikker gennem en cirkel eller et hul? Den berømte Porky Pig-animation, hvor han vinker farvel, mens han springer ud af en række røde ringe, er det perfekte eksempel, og Kilian Valkhof genskabte faktisk det her på CSS-Tricks for et stykke tid tilbage.

Jeg har en lignende idé, men tacklet på en anden måde og med et drys af animation. Jeg synes, det er ret praktisk og giver en pæn svæveeffekt, du kan bruge på noget som din egen avatar.

Kan du se det? Vi skal lave en skaleringsanimation, hvor avataren ser ud til at springe lige ud af den cirkel, den er i. Fedt, ikke? Se ikke på koden, og lad os bygge denne animation sammen trin-for-trin.

HTML: Kun ét element

Hvis du ikke har tjekket koden til demoen, og du spekulerer på, hvor mange divdet tager, så stop lige der, fordi vores opmærkning ikke er andet end et enkelt billedelement:

<img src="" alt="">

Ja, et enkelt element! Den udfordrende del af denne øvelse er at bruge den mindst mulige mængde kode. Hvis du har været følger mig i et stykke tid, bør du være vant til dette. Jeg prøver hårdt på at finde CSS-løsninger, der kan opnås med den mindste, mest vedligeholdelsesvenlige kode.

Jeg skrev en række artikler her på CSS-Tricks, hvor jeg udforsker forskellige hover-effekter ved hjælp af den samme HTML-markering, der indeholder et enkelt element. Jeg går i detaljer med gradienter, maskering, klipning, konturer og endda layoutteknikker. Jeg anbefaler stærkt at tjekke dem ud, fordi jeg vil genbruge mange af tricks i dette indlæg.

En billedfil, der er firkantet med en gennemsigtig baggrund, vil fungere bedst til det, vi laver. Her er den, jeg bruger, hvis du vil starte med den.

Designet af cang

Jeg håber at se masser af eksempler på dette som muligt ved hjælp af rigtige billeder - så del venligst dit endelige resultat i kommentarerne, når du er færdig, så vi kan bygge en samling!

Før vi hopper ind i CSS, lad os først dissekere effekten. Billedet bliver større ved at svæve, så vi vil helt sikkert bruge det transform: scale() derinde. Der er en cirkel bag avataren, og en radial gradient burde gøre tricket. Endelig har vi brug for en måde at skabe en kant i bunden af ​​cirklen, der skaber udseendet af avataren bag cirklen.

Lad os komme på arbejde!

Skalaeffekten

Lad os starte med at tilføje transformationen:

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

Intet kompliceret endnu, vel? Lad os gå videre.

Cirklen

Vi sagde, at baggrunden ville være en radial gradient. Det er perfekt, fordi vi kan skabe hårde stop mellem farverne i en radial gradient, som får det til at se ud som om, vi tegner en cirkel med fuldt optrukne 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);
}

Bemærk CSS-variablen, --b, jeg bruger der. Det repræsenterer tykkelsen af ​​"kanten", som egentlig bare bliver brugt til at definere de hårde farvestop for den røde del af den radiale gradient.

Det næste trin er at lege med gradientstørrelsen, når du svæver. Cirklen skal beholde sin størrelse, efterhånden som billedet vokser. Da vi anvender en scale() transformation, har vi faktisk brug for falde størrelsen af ​​cirklen, fordi den ellers skalerer op med avataren. Så mens billedet skaleres op, har vi brug for gradienten til at skalere ned.

Lad os starte med at definere en CSS-variabel, --f, der definerer "skalafaktoren", og brug den til at indstille størrelsen af ​​cirklen. jeg bruger 1 som standardværdi, da det er den indledende skala for billedet og cirklen, som vi transformerer fra.

Her er en demo for at illustrere tricket. Hold musen over for at se, hvad der sker bag kulisserne:

Jeg tilføjede en tredje farve til radial-gradient for bedre at identificere området af gradienten ved svævning:

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

Nu skal vi placere vores baggrund i midten af ​​cirklen og sørge for, at den fylder hele højden. Jeg kan godt lide at erklære alt direkte på background stenografi egenskab, så vi kan tilføje vores baggrundspositionering og sikre, at den ikke gentager sig ved at slå på disse værdier lige efter radial-gradient():

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

Baggrunden er placeret i midten (50%), har en bredde lig med calc(100%/var(--f)), og har en højde lig med 100%.

Intet skalerer hvornår --f er lig med 1 - igen, vores indledende skala. I mellemtiden fylder gradienten hele beholderens bredde. Når vi øger --f, vokser elementets størrelse - takket være scale() transformere — og gradientens størrelse falder.

Her er, hvad vi får, når vi anvender alt dette på vores demo:

Vi kommer tættere på! Vi har overløbseffekten øverst, men vi mangler stadig at skjule den nederste del af billedet, så det ser ud som om det springer ud af cirklen i stedet for at sidde foran det. Det er den vanskelige del af det hele, og det er det, vi skal gøre næste gang.

Den nederste kant

Jeg prøvede først at tackle dette med border-bottom egenskab, men jeg var ikke i stand til at finde en måde at matche størrelsen af ​​grænsen til størrelsen til cirklen. Her er det bedste, jeg kunne få, og du kan straks se, at det er forkert:

Den egentlige løsning er at bruge outline ejendom. Ja, outline, Ikke border. I en tidligere artikel, jeg viser hvordan outline er kraftfuld og giver os mulighed for at skabe fede svæveeffekter. Kombineret med outline-offset, vi har præcis det, vi skal bruge for vores effekt.

Ideen er at sætte en outline på billedet og juster dets offset for at skabe den nederste kant. Forskydningen vil afhænge af skaleringsfaktoren på samme måde som gradientstørrelsen gjorde.

Nu har vi vores nederste "grænse" (faktisk en outline) kombineret med "grænsen" skabt af gradienten for at skabe en hel cirkel. Vi mangler stadig at skjule dele af outline (fra toppen og siderne), som vi kommer til om et øjeblik.

Her er vores kode indtil videre, inklusive et par flere CSS-variabler, du kan bruge til at konfigurere billedstørrelsen (--s) og "kantfarven" (--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 */
}

Da vi har brug for en cirkulær bundkant, tilføjede vi en border-radius på undersiden, hvilket giver mulighed for outline for at matche gradientens krumning.

Beregningen brugt på outline-offset er meget mere ligetil, end det ser ud. Som standard, outline tegnes uden for af elementets æske. Og i vores tilfælde har vi brug for det overlapning elementet. Mere præcist har vi brug for det til at følge cirklen, der er skabt af gradienten.

Diagram over baggrundsovergangen.

Når vi skalerer elementet, ser vi mellemrummet mellem cirklen og kanten. Lad os ikke glemme, at ideen er at holde cirklen i samme størrelse efter skalatransformationen kører, hvilket efterlader os med den plads, vi vil bruge til at definere omridset forskydning som illustreret i ovenstående figur.

Lad os ikke glemme, at det andet element er skaleret, så vores resultat er også skaleret... hvilket betyder, at vi skal dividere resultatet med f for at få den reelle offsetværdi:

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

Vi tilføjer et negativt tegn, da vi har brug for, at omridset går fra ydersiden til indersiden:

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

Her er en hurtig demo, der viser, hvordan omridset følger gradienten:

Du kan muligvis allerede se det, men vi har stadig brug for bundkonturen til at overlappe cirklen i stedet for at lade den bløde igennem den. Vi kan gøre det ved at fjerne grænsens størrelse fra offset:

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

Nu skal vi finde ud af, hvordan man fjerner den øverste del fra omridset. Vi vil med andre ord kun have den nederste del af billedets outline.

Lad os først tilføje plads i toppen med polstring for at hjælpe med at undgå overlapningen 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 */
}

Der er ingen særlig logik i den øverste polstring. Ideen er at sikre, at omridset ikke rører avatarens hoved. Jeg brugte elementets størrelse til at definere det rum til altid at have den samme proportion.

Bemærk, at jeg har tilføjet content-box værdi for 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 har brug for dette, fordi vi tilføjede polstring, og vi vil kun have baggrunden sat til indholdsboksen, så vi skal udtrykkeligt fortælle, at baggrunden skal stoppe der.

Tilføjelse af CSS-maske til blandingen

Vi nåede den sidste del! Alt vi skal gøre er at skjule nogle stykker, og vi er færdige. Til dette vil vi stole på mask ejendom og selvfølgelig gradienter.

Her er en figur, der illustrerer, hvad vi skal skjule, eller hvad vi skal vise for at være mere nøjagtige

Viser, hvordan masken gælder for den nederste del af cirklen.

Det venstre billede er det, vi har i øjeblikket, og det højre er det, vi ønsker. Den grønne del illustrerer den maske, vi skal anvende på det originale billede for at få det endelige resultat.

Vi kan identificere to dele af vores maske:

  • En cirkulær del i bunden, der har samme dimension og krumning som den radiale gradient, vi brugte til at skabe cirklen bag avataren
  • Et rektangel øverst, der dækker området inde i omridset. Læg mærke til, hvordan omridset er uden for det grønne område øverst - det er den vigtigste del, da det gør det muligt at skære omridset til, så kun den nederste del er synlig.

Her er vores sidste 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 */
}

Lad os nedbryde det mask ejendom. For det første skal du bemærke, at en lignende radial-gradient() fra background ejendom er derinde. Jeg oprettede en ny variabel, --_g, for at de fælles dele gør tingene mindre rodede.

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

Dernæst er der en linear-gradient() også derinde:

--_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 skaber den rektangulære del af masken. Dens bredde er lig med den radiale gradients bredde minus to gange kanttykkelsen:

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

Rektangelets højde er lig med halvdelen, 50%, af elementets størrelse.

Vi har også brug for den lineære gradient placeret i det vandrette centrum (50%) og forskudt fra toppen med samme værdi som omridset forskydning. Jeg oprettede en anden CSS-variabel, --_o, for den offset, vi tidligere definerede:

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

En af de forvirrende ting her er, at vi har brug for en negativ forskydning for omridset (for at flytte det udefra til inde), men en positiv offset for gradienten (for at flytte fra top til bund). Så hvis du undrer dig over, hvorfor vi multiplicerer forskydningen, --_oved -1, nu ved du det!

Her er en demo for at illustrere maskens gradientkonfiguration:

Hold musen over ovenstående og se, hvordan det hele hænger sammen. Den midterste boks illustrerer maskelaget, der består af to gradienter. Forestil dig det som den synlige del af det venstre billede, og du får det endelige resultat til højre!

Indpakning op

Øv, vi er færdige! Og ikke kun endte vi op med en smart svæveanimation, men vi gjorde det hele med en enkelt HTML <img> element. Bare det og mindre end 20 linjers CSS-trickeri!

Selvfølgelig stolede vi på nogle små tricks og matematiske formler for at nå så kompleks en effekt. Men vi vidste præcis, hvad vi skulle gøre, da vi identificerede de stykker, vi havde brug for på forhånd.

Kunne vi have forenklet CSS, hvis vi tillod os selv mere HTML? Absolut. Men vi er her for at lære nye CSS-tricks! Dette var en god øvelse til at udforske CSS-gradienter, maskering, outline ejendommens adfærd, transformationer og en hel masse mere. Hvis du følte dig fortabt på noget tidspunkt, så tjek helt sikkert ud min serie der bruger de samme generelle begreber. Nogle gange hjælper det at se flere eksempler og bruge cases til at køre en pointe hjem.

Jeg vil efterlade dig med en sidste demo, der bruger billeder af populære CSS-udviklere. Glem ikke at vise mig en demo med dit eget billede, så jeg kan tilføje det til samlingen!

Chat med os

Hej! Hvordan kan jeg hjælpe dig?