Xlera8

Un efect de hover elegant pentru avatarul tău

Știți acel tip de efect în care capul cuiva trece printr-un cerc sau o gaură? Celebra animație Porky Pig în care își dă rămas bun în timp ce iese dintr-o serie de inele roșii este exemplul perfect și Kilian Valkhof chiar a recreat asta aici pe CSS-Tricks cu ceva timp în urmă.

Am o idee similară, dar am abordat într-un mod diferit și cu un pic de animație. Cred că este destul de practic și oferă un efect de hover pe care îl poți folosi pe ceva precum propriul tău avatar.

Vezi asta? Vom face o animație de scalare în care avatarul pare să iasă imediat din cercul în care se află. Cool, nu? Nu te uita la cod și hai să construim această animație împreună pas cu pas.

HTML: doar un element

Dacă nu ați verificat codul demo-ului și vă întrebați câți divva dura acest lucru, apoi opriți-vă chiar acolo, deoarece marcajul nostru nu este altceva decât un singur element de imagine:

<img src="" alt="">

Da, un singur element! Partea provocatoare a acestui exercițiu este utilizarea celei mai mici cantități de cod posibil. Dacă ați fost urmarindu-ma pentru o vreme, ar trebui să fii obișnuit cu asta. Încerc din greu să găsesc soluții CSS care să poată fi realizate cu cel mai mic și mai ușor de întreținut cod posibil.

I-am scris o serie de articole aici pe CSS-Tricks unde explorez diferite efecte de hover folosind același marcaj HTML care conține un singur element. Intru în detalii despre degrade, mascare, tăiere, contururi și chiar tehnici de aspect. Recomand cu căldură să le verifici pentru că voi reutiliza multe dintre trucurile din această postare.

Un fișier imagine care este pătrat cu un fundal transparent va funcționa cel mai bine pentru ceea ce facem. Iată-l pe care îl folosesc dacă vrei să începi cu asta.

Proiectat de cang

Sper să văd o mulțime de exemple în acest sens folosind imagini reale – așa că vă rugăm să împărtășiți rezultatul final în comentarii când ați terminat, astfel încât să putem construi o colecție!

Înainte de a sări în CSS, să disecăm mai întâi efectul. Imaginea devine mai mare la trecerea cursorului, așa că o vom folosi cu siguranță transform: scale() acolo. Există un cerc în spatele avatarului, iar un gradient radial ar trebui să facă treaba. În cele din urmă, avem nevoie de o modalitate de a crea un chenar în partea de jos a cercului, care să creeze aspectul avatarului în spatele cercului.

Sa trecem la treaba!

Efectul de scară

Să începem prin a adăuga transformarea:

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

Nimic complicat încă, nu? Sa trecem peste.

Cercul

Am spus că fundalul va fi un gradient radial. Este perfect pentru că putem crea opriri dure între culorile unui gradient radial, care fac să pară că desenăm un cerc cu linii continue.

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

Rețineți variabila CSS, --b, eu folosesc acolo. Reprezintă grosimea „graniței” care este într-adevăr folosită pentru a defini opririle dure de culoare pentru partea roșie a gradientului radial.

Următorul pas este să te joci cu dimensiunea gradientului la hover. Cercul trebuie să-și păstreze dimensiunea pe măsură ce imaginea crește. Deoarece aplicăm a scale() transformare, chiar trebuie scădea dimensiunea cercului, deoarece altfel crește cu avatarul. Deci, în timp ce imaginea crește, avem nevoie ca gradientul să se reducă.

Să începem prin a defini o variabilă CSS, --f, care definește „factorul de scară” și îl folosește pentru a seta dimensiunea cercului. eu folosesc 1 ca valoare implicită, deoarece aceasta este scara inițială pentru imagine și cercul din care transformăm.

Iată o demonstrație pentru a ilustra trucul. Plasați cursorul pentru a vedea ce se întâmplă în culise:

Am adăugat o a treia culoare la radial-gradient pentru a identifica mai bine zona gradientului la hover:

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

Acum trebuie să ne poziționăm fundalul în centrul cercului și să ne asigurăm că ocupă toată înălțimea. Îmi place să declar totul direct pe background proprietatea scurtă, astfel încât să putem adăuga poziționarea noastră de fundal și să ne asigurăm că nu se repetă prin lipirea acestor valori imediat după radial-gradient():

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

Fundalul este plasat în centru (50%), are o lățime egală cu calc(100%/var(--f)), și are o înălțime egală cu 100%.

Nimic nu se scalează când --f este egal cu 1 — din nou, scara noastră inițială. Între timp, gradientul ocupă toată lățimea containerului. Când creștem --f, dimensiunea elementului crește — datorită scale() transformă — iar dimensiunea gradientului scade.

Iată ce obținem când aplicăm toate acestea la demonstrația noastră:

Ne apropiem! Avem efectul de debordare în partea de sus, dar trebuie totuși să ascundem partea de jos a imaginii, astfel încât să pară că iese din cerc în loc să stea în fața acesteia. Aceasta este partea dificilă a întregii chestii și este ceea ce vom face în continuare.

Chenarul de jos

Am încercat mai întâi să rezolv asta cu border-bottom proprietate, dar nu am reușit să găsesc o modalitate de a potrivi dimensiunea chenarului cu dimensiunea cercului. Iată tot ce am putut obține și puteți vedea imediat că este greșit:

Soluția reală este utilizarea outline proprietate. Da, outline, Nu border. În un articol anterior, arăt cum outline este puternic și ne permite să creăm efecte de hover cool. Combinat cu outline-offset, avem exact ceea ce ne trebuie pentru efectul nostru.

Ideea este de a seta un outline pe imagine și ajustați offset-ul pentru a crea marginea de jos. Offset-ul va depinde de factorul de scalare la fel ca dimensiunea gradientului.

Acum avem „granița” inferioară (de fapt un outline) combinat cu „chenarul” creat de gradient pentru a crea un cerc complet. Încă trebuie să ascundem porțiuni din outline (din partea de sus și din lateral), la care vom ajunge într-o clipă.

Iată codul nostru până acum, inclusiv câteva variabile CSS pe care le puteți utiliza pentru a configura dimensiunea imaginii (--s) și culoarea „chenar” (--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 */
}

Deoarece avem nevoie de o margine de jos circulară, am adăugat a border-radius pe partea de jos, permițând outline pentru a se potrivi cu curbura gradientului.

Calculul folosit pe outline-offset este mult mai simplu decât pare. În mod implicit, outline este desenat exterior a cutiei elementului. Și în cazul nostru, avem nevoie suprapune elementul. Mai exact, avem nevoie de el pentru a urmări cercul creat de gradient.

Diagrama tranziției de fundal.

Când scalam elementul, vedem spațiul dintre cerc și margine. Să nu uităm că ideea este să menținem cercul la aceeași dimensiune după rularea transformării la scară, ceea ce ne lasă cu spațiul pe care îl vom folosi pentru a defini offset-ul conturului, așa cum este ilustrat în figura de mai sus.

Să nu uităm că al doilea element este scalat, deci rezultatul nostru este, de asemenea, scalat... ceea ce înseamnă că trebuie să împărțim rezultatul la f pentru a obține valoarea reală a offsetului:

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

Adăugăm un semn negativ, deoarece avem nevoie ca conturul să meargă din exterior spre interior:

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

Iată o demonstrație rapidă care arată cum conturul urmează gradientul:

Poate îl vedeți deja, dar avem nevoie de conturul de jos pentru a suprapune cercul, în loc să-l lăsăm să curgă prin el. Putem face asta eliminând dimensiunea chenarului din offset:

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

Acum trebuie să găsim cum să scoatem partea de sus din contur. Cu alte cuvinte, vrem doar partea de jos a imaginii outline.

Mai întâi, să adăugăm spațiu în partea de sus cu căptușeală pentru a evita suprapunerea în partea de sus:

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

Nu există o logică specială pentru căptușeala de sus. Ideea este să vă asigurați că conturul nu atinge capul avatarului. Am folosit dimensiunea elementului pentru a defini acel spațiu pentru a avea întotdeauna aceeași proporție.

Rețineți că am adăugat content-box valoare pentru 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;

Avem nevoie de asta deoarece am adăugat umplutură și vrem doar ca fundalul să fie setat în caseta de conținut, așa că trebuie să spunem explicit fundalului să se oprească acolo.

Adăugarea măștii CSS la mix

Am ajuns la ultima parte! Tot ce trebuie să facem este să ascundem câteva bucăți și am terminat. Pentru aceasta, ne vom baza pe mask proprietate și, bineînțeles, gradienți.

Iată o cifră pentru a ilustra ceea ce trebuie să ascundem sau ce trebuie să arătăm pentru a fi mai precis

Arată cum se aplică masca în partea de jos a cercului.

Imaginea din stânga este ceea ce avem în prezent, iar cea din dreapta este ceea ce ne dorim. Partea verde ilustrează masca pe care trebuie să o aplicăm imaginii originale pentru a obține rezultatul final.

Putem identifica două părți ale măștii noastre:

  • O parte circulară în partea de jos care are aceeași dimensiune și curbură ca și gradientul radial pe care l-am folosit pentru a crea cercul din spatele avatarului
  • Un dreptunghi în partea de sus care acoperă zona din interiorul conturului. Observați cum conturul este în afara zonei verzi din partea de sus - aceasta este cea mai importantă parte, deoarece permite tăierea conturului astfel încât doar partea de jos să fie vizibilă.

Iată CSS-ul nostru final:

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

Să defalcăm asta mask proprietate. Pentru început, observați că un similar radial-gradient() de la background proprietatea este acolo. Am creat o nouă variabilă, --_g, pentru ca părțile comune să facă lucrurile mai puțin aglomerate.

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

În continuare, există un linear-gradient() si acolo:

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

Aceasta creează partea dreptunghiulară a măștii. Lățimea sa este egală cu lățimea gradientului radial minus de două ori grosimea marginii:

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

Înălțimea dreptunghiului este egală cu jumătate, 50%, de dimensiunea elementului.

Avem nevoie și de gradientul liniar plasat în centrul orizontal (50%) și decalat din partea de sus cu aceeași valoare ca decalajul conturului. Am creat o altă variabilă CSS, --_o, pentru offset-ul definit anterior:

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

Unul dintre lucrurile confuze aici este că avem nevoie de a negativ offset pentru contur (pentru a-l muta din exterior în interior) dar a pozitiv offset pentru gradient (pentru a muta de sus în jos). Deci, dacă vă întrebați de ce înmulțim compensarea, --_o, de -1, ei bine, acum știi!

Iată o demonstrație pentru a ilustra configurația gradientului măștii:

Plasați cursorul de mai sus și vedeți cum totul se mișcă împreună. Caseta din mijloc ilustrează stratul de mască compus din doi gradienți. Imaginați-vă că este partea vizibilă a imaginii din stânga și obțineți rezultatul final din dreapta!

La finalul

Uf, am terminat! Și nu numai că am terminat cu o animație cu hover, dar am făcut totul cu un singur HTML <img> element. Doar atât și mai puțin de 20 de linii de înșelătorie CSS!

Sigur, ne-am bazat pe câteva trucuri și formule matematice pentru a ajunge la un efect atât de complex. Dar știam exact ce să facem, deoarece am identificat din față piesele de care aveam nevoie.

Am fi putut simplifica CSS-ul dacă ne-am permite mai mult HTML? Absolut. Dar suntem aici pentru a învăța noi trucuri CSS! Acesta a fost un exercițiu bun pentru a explora gradienții CSS, mascarea, outline comportamentul proprietății, transformările și multe altele. Dacă te-ai simțit pierdut în orice moment, atunci verifică cu siguranță seria mea care folosește aceleași concepte generale. Uneori, vă ajută să vedeți mai multe exemple și cazuri de utilizare pentru a conduce un punct acasă.

Vă voi lăsa cu o ultimă demonstrație care folosește fotografii ale dezvoltatorilor CSS populari. Nu uita să-mi arăți un demo cu propria ta imagine ca să o pot adăuga la colecție!

Chat cu noi

Bună! Cu ce ​​​​vă pot ajuta?