Este o întrebare pe care am auzit-o adresată destul de des: Este posibil să creați umbre din degrade în loc de culori solide? Nu există nicio proprietate CSS specifică care să facă acest lucru (crede-mă, m-am uitat) și orice postare pe blog pe care o găsești despre ea este practic o mulțime de trucuri CSS pentru a aproxima un gradient. De fapt, vom acoperi unele dintre acestea pe măsură ce mergem.
Dar mai intai… un alt articol despre umbrele gradiente? Într-adevăr?
Da, acesta este încă o postare pe acest subiect, dar este diferit. Împreună, vom depăși limitele pentru a obține o soluție care să acopere ceva ce nu am văzut nicăieri altundeva: transparență. Majoritatea trucurilor funcționează dacă elementul are un fundal netransparent, dar ce se întâmplă dacă avem un fundal transparent? Vom explora acest caz aici!
Înainte de a începe, permiteți-mi să vă prezint generatorul meu de umbre de gradient. Tot ce trebuie să faceți este să ajustați configurația și să obțineți codul. Dar urmați, pentru că vă voi ajuta să înțelegeți toată logica din spatele codului generat.
Cuprins
Soluție netransparentă
Să începem cu soluția care va funcționa în 80% din majoritatea cazurilor. Cel mai tipic caz: utilizați un element cu un fundal și trebuie să adăugați o umbră gradient. Nu există probleme de transparență de luat în considerare acolo.
Soluția este să te bazezi pe un pseudo-element în care este definit gradientul. Îl așezi în spatele elementului real și aplicați un filtru de estompare.
.box { position: relative;
}
.box::before { content: ""; position: absolute; inset: -5px; /* control the spread */ transform: translate(10px, 8px); /* control the offsets */ z-index: -1; /* place the element behind */ background: /* your gradient here */; filter: blur(10px); /* control the blur */
}
Pare mult cod și asta pentru că este. Iată cum am fi putut face asta cu un box-shadow
în schimb, dacă am folosi o culoare solidă în loc de un gradient.
box-shadow: 10px 8px 10px 5px orange;
Acest lucru ar trebui să vă ofere o idee bună despre ceea ce fac valorile din primul fragment. Avem decalaje X și Y, raza de estompare și distanța de răspândire. Rețineți că avem nevoie de o valoare negativă pentru distanța de răspândire care vine de la inset
proprietate.
Iată o demonstrație care arată umbra gradientului lângă un clasic box-shadow
:
Dacă te uiți cu atenție, vei observa că ambele umbre sunt puțin diferite, în special partea de estompare. Nu este o surpriză pentru că sunt destul de sigur că filter
algoritmul proprietății funcționează diferit decât cel pentru box-shadow
. Nu este mare lucru, deoarece rezultatul este, în cele din urmă, destul de asemănător.
Această soluție este bună, dar are încă câteva dezavantaje legate de z-index: -1
declaraţie. Da este „context de stivuire” se întâmplă acolo!
am aplicat a transform
la elementul principal, și boom! Umbra nu mai este sub element. Acesta nu este o eroare, ci rezultatul logic al unui context de stivuire. Nu vă faceți griji, nu voi începe o explicație plictisitoare despre contextul de stivuire (Am făcut deja asta într-un fir Stack Overflow), dar vă voi arăta în continuare cum să o rezolvați.
Prima soluție pe care o recomand este să folosești un 3D transform
:
.box { position: relative; transform-style: preserve-3d;
}
.box::before { content: ""; position: absolute; inset: -5px; transform: translate3d(10px, 8px, -1px); /* (X, Y, Z) */ background: /* .. */; filter: blur(10px);
}
În loc de a folosi z-index: -1
, vom folosi o translație negativă de-a lungul axei Z. Vom pune totul înăuntru translate3d()
. Nu uitați să utilizați transform-style: preserve-3d
pe elementul principal; altfel, 3D transform
nu va avea efect.
Din câte știu eu, nu există niciun efect secundar la această soluție... dar poate că vedeți unul. Dacă este cazul, împărtășește-l în secțiunea de comentarii și hai să încercăm să găsim o remediere!
Dacă dintr-un motiv oarecare nu puteți utiliza un 3D transform
, cealaltă soluție este să te bazezi pe două pseudo-elemente — ::before
și ::after
. Unul creează umbra gradient, iar celălalt reproduce fundalul principal (și alte stiluri de care ai putea avea nevoie). În acest fel, putem controla cu ușurință ordinea de stivuire a ambelor pseudo-elemente.
.box { position: relative; z-index: 0; /* We force a stacking context */
}
/* Creates the shadow */
.box::before { content: ""; position: absolute; z-index: -2; inset: -5px; transform: translate(10px, 8px); background: /* .. */; filter: blur(10px);
}
/* Reproduces the main element styles */
.box::after { content: """; position: absolute; z-index: -1; inset: 0; /* Inherit all the decorations defined on the main element */ background: inherit; border: inherit; box-shadow: inherit;
}
Este important să rețineți că suntem forțând elementul principal pentru a crea un context de stivuire prin declarare z-index: 0
, Sau orice altă proprietate care face același lucru, pe el. De asemenea, nu uitați că pseudo-elementele consideră căsuța de umplutură a elementului principal ca referință. Deci, dacă elementul principal are o margine, trebuie să țineți cont de acest lucru atunci când definiți stilurile de pseudo-element. Veți observa că folosesc inset: -2px
on ::after
pentru a ține cont de granița definită pe elementul principal.
După cum am spus, această soluție este probabil suficient de bună în majoritatea cazurilor în care doriți o umbră gradient, atâta timp cât nu trebuie să susțineți transparența. Dar suntem aici pentru provocare și pentru a depăși limitele, așa că chiar dacă nu aveți nevoie de ceea ce urmează, rămâneți cu mine. Probabil veți învăța noi trucuri CSS pe care le puteți folosi în altă parte.
Soluție transparentă
Să reluăm de unde am rămas în 3D transform
și eliminați fundalul din elementul principal. Voi începe cu o umbră care are atât decalaje cât și distanță de răspândire egală cu 0
.
Ideea este de a găsi o modalitate de a tăia sau ascunde totul în interiorul zonei elementului (în interiorul chenarului verde), păstrând în același timp ceea ce este în exterior. Vom folosi clip-path
pentru asta. Dar s-ar putea să vă întrebați cum clip-path
poate face o tăietură în interiorul un element.
Într-adevăr, nu există nicio modalitate de a face asta, dar îl putem simula folosind un anumit model de poligon:
clip-path: polygon(-100vmax -100vmax,100vmax -100vmax,100vmax 100vmax,-100vmax 100vmax,-100vmax -100vmax,0 0,0 100%,100% 100%,100% 0,0 0)
Tada! Avem o umbră gradient care acceptă transparența. Tot ce am făcut a fost să adăugăm un clip-path
la codul anterior. Iată o figură pentru a ilustra partea poligonului.
Zona albastră este partea vizibilă după aplicarea clip-path
. Folosesc doar culoarea albastră pentru a ilustra conceptul, dar, în realitate, vom vedea doar umbra în interiorul acelei zone. După cum puteți vedea, avem patru puncte definite cu o valoare mare (B
). Marea mea valoare este 100vmax
, dar poate fi orice valoare mare doriți. Ideea este să ne asigurăm că avem suficient spațiu pentru umbră. Avem și patru puncte care sunt colțurile pseudo-elementului.
Săgețile ilustrează calea care definește poligonul. Pornim de la (-B, -B)
până ajungem (0,0)
. În total, avem nevoie de 10 puncte. Nu opt puncte pentru că două puncte se repetă de două ori în traseu ((-B,-B)
și (0,0)
).
Mai există inca un lucru ne-a rămas de făcut, și este să ținem cont de distanța de răspândire și de decalaje. Singurul motiv pentru care demo-ul de mai sus funcționează este că este un caz particular în care offset-urile și distanța de răspândire sunt egale cu 0
.
Să definim răspândirea și să vedem ce se întâmplă. Amintiți-vă că folosim inset
cu o valoare negativă pentru a face acest lucru:
Pseudo-elementul este acum mai mare decât elementul principal, deci clip-path
reduce mai mult decât avem nevoie. Amintiți-vă, trebuie întotdeauna să tăiem piesa în interiorul elementul principal (zona din interiorul chenarului verde al exemplului). Trebuie să ajustăm poziția celor patru puncte din interior clip-path
.
.box { --s: 10px; /* the spread */ position: relative;
}
.box::before { inset: calc(-1 * var(--s)); clip-path: polygon( -100vmax -100vmax, 100vmax -100vmax, 100vmax 100vmax, -100vmax 100vmax, -100vmax -100vmax, calc(0px + var(--s)) calc(0px + var(--s)), calc(0px + var(--s)) calc(100% - var(--s)), calc(100% - var(--s)) calc(100% - var(--s)), calc(100% - var(--s)) calc(0px + var(--s)), calc(0px + var(--s)) calc(0px + var(--s)) );
}
Am definit o variabilă CSS, --s
, pentru distanța de răspândire și a actualizat punctele poligonului. Nu am atins punctele în care folosesc valoarea mare. Actualizez doar punctele care definesc colțurile pseudo-elementului. Măresc toate valorile zero cu --s
și micșorați 100%
valori prin --s
.
Este aceeași logică cu offset-urile. Când traducem pseudo-elementul, umbra este în afara alinierii și trebuie să rectificăm din nou poligonul și să mutăm punctele în direcția opusă.
.box { --s: 10px; /* the spread */ --x: 10px; /* X offset */ --y: 8px; /* Y offset */ position: relative;
}
.box::before { inset: calc(-1 * var(--s)); transform: translate3d(var(--x), var(--y), -1px); clip-path: polygon( -100vmax -100vmax, 100vmax -100vmax, 100vmax 100vmax, -100vmax 100vmax, -100vmax -100vmax, calc(0px + var(--s) - var(--x)) calc(0px + var(--s) - var(--y)), calc(0px + var(--s) - var(--x)) calc(100% - var(--s) - var(--y)), calc(100% - var(--s) - var(--x)) calc(100% - var(--s) - var(--y)), calc(100% - var(--s) - var(--x)) calc(0px + var(--s) - var(--y)), calc(0px + var(--s) - var(--x)) calc(0px + var(--s) - var(--y)) );
}
Mai sunt două variabile pentru compensații: --x
și --y
. Le folosim în interior transform
și actualizăm, de asemenea clip-path
valorile. Încă nu atingem punctele poligonului cu valori mari, dar le compensăm pe toate celelalte — reducem --x
din coordonatele X și --y
din coordonatele Y.
Acum tot ce trebuie să facem este să actualizăm câteva variabile pentru a controla umbra gradientului. Și în timp ce suntem la asta, să facem și raza de estompare o variabilă:
Mai avem nevoie de 3D
transform
truc?
Totul depinde de graniță. Nu uitați că referința pentru un pseudo-element este caseta de umplutură, așa că dacă aplicați un chenar elementului principal, veți avea o suprapunere. Fie păstrați 3D-ul transform
trucați sau actualizați inset
valoare pentru a ține cont de graniță.
Iată demonstrația anterioară cu o actualizare inset
valoare în locul 3D transform
:
Aș spune că aceasta este o modalitate mai potrivită de parcurs, deoarece distanța de răspândire va fi mai precisă, deoarece pornește de la bordura-box în loc de padding-box. Dar va trebui să ajustați inset
valoare în funcție de chenarul elementului principal. Uneori, marginea elementului este necunoscută și trebuie să utilizați soluția anterioară.
Cu soluția anterioară netransparentă, este posibil să vă confruntați cu o problemă de context de stivuire. Și cu soluția transparentă, este posibil să vă confruntați cu o problemă de frontieră. Acum aveți opțiuni și modalități de a rezolva aceste probleme. Trucul de transformare 3D este soluția mea preferată, deoarece rezolvă toate problemele (Generatorul online o va lua in considerare si)
Adăugarea unei raze a marginii
Dacă încerci să adaugi border-radius
la element atunci când folosim soluția netransparentă cu care am început, este o sarcină destul de banală. Tot ce trebuie să faceți este să moșteniți aceeași valoare de la elementul principal și ați terminat.
Chiar dacă nu aveți o rază de frontieră, este o idee bună să definiți border-radius: inherit
. Asta explică orice potențial border-radius
s-ar putea să doriți să adăugați mai târziu sau o rază de frontieră care vine din altă parte.
Este o altă poveste când ai de-a face cu soluția transparentă. Din păcate, înseamnă să găsești o altă soluție pentru că clip-path
nu poate face față curburilor. Asta înseamnă că nu vom putea tăia zona din interiorul elementului principal.
Vom introduce mask
proprietatea amestecului.
Această parte a fost foarte plictisitoare și m-am chinuit să găsesc o soluție generală pe care să nu se bazeze numere magice. Am ajuns la o soluție foarte complexă care folosește doar un pseudo-element, dar codul era o bucată de spaghete care acoperă doar câteva cazuri particulare. Nu cred că merită să explorez acel traseu.
Am decis să inserez un element suplimentar de dragul unui cod mai simplu. Iată marcajul:
<div class="box"> <sh></sh>
</div>
Folosesc un element personalizat, <sh>
, pentru a evita orice conflict potențial cu CSS extern. Aș fi putut folosi un <div>
, dar din moment ce este un element comun, poate fi vizat cu ușurință de o altă regulă CSS care vine din altă parte care ne poate sparge codul.
Primul pas este poziționarea <sh>
element și creează în mod intenționat un debordare:
.box { --r: 50px; position: relative; border-radius: var(--r);
}
.box sh { position: absolute; inset: -150px; border: 150px solid #0000; border-radius: calc(150px + var(--r));
}
Codul poate părea puțin ciudat, dar vom ajunge la logica din spatele lui pe măsură ce mergem. Apoi, creăm umbra gradient folosind un pseudo-element de <sh>
.
.box { --r: 50px; position: relative; border-radius: var(--r); transform-style: preserve-3d;
}
.box sh { position: absolute; inset: -150px; border: 150px solid #0000; border-radius: calc(150px + var(--r)); transform: translateZ(-1px)
}
.box sh::before { content: ""; position: absolute; inset: -5px; border-radius: var(--r); background: /* Your gradient */; filter: blur(10px); transform: translate(10px,8px);
}
După cum puteți vedea, pseudo-elementul folosește același cod ca toate exemplele anterioare. Singura diferență este 3D transform
definite pe <sh>
element în loc de pseudo-element. Pentru moment, avem o umbră gradient fără caracteristica de transparență:
Rețineți că zona de <sh>
elementul este definit cu conturul negru. De ce fac asta? Pentru că astfel, pot aplica a mask
pe ea pentru a ascunde partea din interiorul zonei verzi și a păstra partea care se debordează unde trebuie să vedem umbra.
Știu că este puțin complicat, dar spre deosebire de asta clip-path
, mask
proprietatea nu ține cont de zonă exterior un element pentru a arăta și a ascunde lucrurile. De aceea am fost obligat să introduc elementul suplimentar — pentru a simula zona „exterior”.
De asemenea, rețineți că folosesc o combinație de border
și inset
pentru a defini zona respectivă. Acest lucru îmi permite să păstrez padding-box-ul acelui element suplimentar la fel ca și elementul principal, astfel încât pseudo-elementul să nu aibă nevoie de calcule suplimentare.
Un alt lucru util pe care îl obținem din utilizarea unui element suplimentar este că elementul este fix și numai pseudo-elementul se mișcă (folosind translate
). Acest lucru îmi va permite să definesc cu ușurință masca, care este ultimul pasul acestui truc.
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
mask-composite: exclude;
Este gata! Avem umbra noastră în gradient și o acceptă border-radius
! Probabil te așteptai la un complex mask
valoare cu o mulțime de degrade, dar nu! Avem nevoie doar de doi gradienți simpli și a mask-composite
pentru a finaliza magia.
Să izolăm <sh>
element pentru a înțelege ce se întâmplă acolo:
.box sh { position: absolute; inset: -150px; border: 150px solid red; background: lightblue; border-radius: calc(150px + var(--r));
}
Iată ce primim:
Observați cum raza interioară se potrivește cu cea a elementului principal border-radius
. Am definit o graniță mare (150px
) Și un border-radius
egal cu granita mare la care se adauga raza elementului principal. La exterior am o raza egala cu 150px + R
. Pe dinăuntru, am 150px + R - 150px = R
.
Trebuie să ascundem partea interioară (albastru) și să ne asigurăm că partea de margine (roșie) este încă vizibilă. Pentru a face asta, am definit două straturi de mască — Unul care acoperă doar zona casetei de conținut și altul care acoperă zona casetei de margine (valoarea implicită). Apoi m-am exclus unul de altul pentru a dezvălui granița.
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
mask-composite: exclude;
Am folosit aceeași tehnică creați o chenar care suportă degrade și border-radius
. Ana Tudor are și un articol bun despre compozitul de mascare pe care vă invit să citiți.
Există dezavantaje ale acestei metode?
Da, acest lucru cu siguranță nu este perfect. Prima problemă cu care vă puteți confrunta este legată de utilizarea unui chenar pe elementul principal. Acest lucru poate crea o mică nealiniere a razelor dacă nu țineți cont de aceasta. Avem această problemă în exemplul nostru, dar poate cu greu o puteți observa.
Remedierea este relativ ușoară: adăugați lățimea chenarului pentru <sh>
elementului inset
.
.box { --r: 50px; border-radius: var(--r); border: 2px solid;
}
.box sh { position: absolute; inset: -152px; /* 150px + 2px */ border: 150px solid #0000; border-radius: calc(150px + var(--r));
}
Un alt dezavantaj este valoarea mare pe care o folosim pentru graniță (150px
în exemplu). Această valoare ar trebui să fie suficient de mare pentru a conține umbra, dar nu prea mare pentru a evita problemele de depășire și bara de derulare. Din fericire, generatorul online va calcula valoarea optimă luând în considerare toți parametrii.
Ultimul dezavantaj de care sunt conștient este atunci când lucrați cu un complex border-radius
. De exemplu, dacă doriți o rază diferită aplicată fiecărui colț, trebuie să definiți o variabilă pentru fiecare latură. Nu este chiar un dezavantaj, presupun, dar vă poate face codul un pic mai greu de întreținut.
.box { --r-top: 10px; --r-right: 40px; --r-bottom: 30px; --r-left: 20px; border-radius: var(--r-top) var(--r-right) var(--r-bottom) var(--r-left);
}
.box sh { border-radius: calc(150px + var(--r-top)) calc(150px + var(--r-right)) calc(150px + var(--r-bottom)) calc(150px + var(--r-left));
}
.box sh:before { border-radius: var(--r-top) var(--r-right) var(--r-bottom) var(--r-left);
}
Generatorul online ia în considerare doar o rază uniformă de dragul simplității, dar acum știți cum să modificați codul dacă doriți să luați în considerare o configurație complexă de rază.
La finalul
Am ajuns la final! Magia din spatele umbrelor gradiente nu mai este un mister. Am încercat să acopăr toate posibilitățile și orice posibile probleme cu care te-ai putea confrunta. Dacă am omis ceva sau descoperiți vreo problemă, nu ezitați să o raportați în secțiunea de comentarii și o voi verifica.
Din nou, multe dintre acestea sunt probabil exagerate, având în vedere că soluția de facto va acoperi majoritatea cazurilor dvs. de utilizare. Cu toate acestea, este bine să știi „de ce” și „cum” din spatele trucului și cum să-i depășești limitările. În plus, ne-am exercitat bine jucând cu tăierea și mascarea CSS.
Și, desigur, ai generatorul online puteți ajunge oricând doriți pentru a evita bătălia de cap.
- Distribuție de conținut bazat pe SEO și PR. Amplifică-te astăzi.
- Platoblockchain. Web3 Metaverse Intelligence. Cunoștințe amplificate. Accesați Aici.
- Sursa: https://css-tricks.com/different-ways-to-get-css-gradient-shadows/
- 1
- 10
- 11
- 3d
- 7
- 9
- 98
- a
- Capabil
- Despre Noi
- despre
- mai sus
- Absolut
- Conform
- Cont
- Conturi
- precis
- de fapt
- Suplimentar
- După
- Algoritmul
- TOATE
- permite
- deja
- mereu
- Ana
- și
- O alta
- oriunde
- aplicat
- Aplică
- Aplicarea
- ZONĂ
- în jurul
- articol
- evita
- fundal
- Pe scurt
- deoarece
- înainte
- în spatele
- Crede
- de mai jos
- Mare
- mai mare
- Pic
- Negru
- Blog
- Albastru
- estompare
- frontieră
- Plictisitor
- Cutie
- Pauză
- Bug
- calcula
- nu poti
- caz
- cazuri
- contesta
- verifica
- clasic
- clip-path
- îndeaproape
- cod
- culoare
- combinaţie
- venire
- comentariu
- Comun
- Completă
- complex
- concept
- Configuraţie
- conflict
- Lua în considerare
- luand in considerare
- consideră
- conţinut
- context
- Control
- Colț
- colțuri
- ar putea
- înscrie-te la cursul
- acoperi
- acoperă
- crea
- creează
- CSS
- CSS Trucuri
- personalizat
- Tăiat
- reduceri
- afacere
- abuzive
- hotărât
- scădea
- Mod implicit
- definit
- defineste
- definire
- categoric
- Demo
- depinde de
- FĂCUT
- diferenţă
- diferit
- direcţie
- descoperi
- distanţă
- Nu
- face
- Dont
- dezavantaje
- fiecare
- Mai devreme
- cu ușurință
- efect
- oricare
- în altă parte
- suficient de
- asigura
- mai ales
- Eter (ETH)
- Chiar
- tot
- exemplu
- exemple
- exclus
- Exercita
- de aşteptat
- explicație
- explora
- Explorarea
- extern
- suplimentar
- Față
- facto
- destul de
- Favorite
- Caracteristică
- puțini
- Figura
- filtru
- Găsi
- descoperire
- First
- Repara
- fixată
- urma
- Forţarea
- Gratuit
- din
- General
- generată
- obține
- Da
- Go
- merge
- bine
- gradienți
- Verde
- se întâmplă
- ajutor
- aici
- Ascunde
- Cum
- Cum Pentru a
- HTML
- HTTPS
- BOLNAV
- idee
- important
- in
- Crește
- in schimb
- introduce
- invita
- problema
- probleme de
- IT
- A pastra
- păstrare
- Cunoaște
- Nume
- straturi
- AFLAȚI
- Probabil
- limitări
- Limitele
- mic
- Lung
- mai lung
- Uite
- uitat
- Se pare
- Lot
- magie
- Principal
- menține
- Majoritate
- face
- masca
- mijloace
- metodă
- ar putea
- modifica
- moment
- mai mult
- cele mai multe
- muta
- în mişcare
- Mozilla
- Mister
- Nevoie
- negativ
- cu toate acestea
- Nou
- următor
- compensa
- ONE
- on-line
- opus
- optimă
- Opţiuni
- Portocaliu
- comandă
- Altele
- Altele
- in caz contrar
- schiță
- exterior
- Învinge
- parametrii
- parte
- special
- cale
- Model
- Perfect
- poate
- alege
- Loc
- Plato
- Informații despre date Platon
- PlatoData
- joc
- "vă rog"
- la care se adauga
- puncte
- Poligon
- poziţie
- posibilităţile de
- posibil
- Post
- potenţial
- destul de
- precedent
- probabil
- proprietate
- Împinge
- pune
- întrebare
- ajunge
- atins
- Citeste
- Realitate
- motiv
- recomanda
- Roșu
- reduce
- legate de
- relativ
- se bazează
- minte
- scoate
- repetat
- raportează
- rezultat
- dezvălui
- Traseul
- Regula
- Said
- sake
- acelaşi
- Secțiune
- Umbră
- Distribuie
- să
- Arăta
- asemănător
- simplu
- simplitate
- întrucât
- mic
- So
- solid
- soluţie
- unele
- ceva
- undeva
- Spaţiu
- specific
- răspândire
- stivui
- stivuire
- Începe
- început
- începe
- şedere
- Pas
- Încă
- Poveste
- potrivit
- a sustine
- Sprijină
- surpriză
- Lua
- vizate
- Sarcină
- Zona
- lucru
- lucruri
- Crede
- la
- împreună
- de asemenea
- subiect
- Total
- atingeţi
- Transforma
- Traduceți
- Traducere
- Transparență
- transparent
- trucuri
- adevărat
- tipic
- înţelege
- Actualizează
- actualizat
- us
- utilizare
- valoare
- Valori
- vizibil
- modalități de
- Ce
- Ce este
- care
- în timp ce
- voi
- fără
- Apartamente
- de lucru
- fabrică
- valoare
- X
- Ta
- indexul z
- zephyrnet
- zero