Det er et spørsmål jeg ofte hører stilt: Er det mulig å lage skygger fra gradienter i stedet for solide farger? Det er ingen spesifikk CSS-egenskap som gjør dette (tro meg, jeg har sett) og ethvert blogginnlegg du finner om det er i utgangspunktet mange CSS-triks for å tilnærme en gradient. Vi vil faktisk dekke noen av dem mens vi går.
Men først… en annen artikkel om gradientskygger? Egentlig?
Ja, dette er nok et innlegg om emnet, men det er annerledes. Sammen skal vi presse grensene for å få en løsning som dekker noe jeg ikke har sett andre steder: åpenhet. De fleste triksene fungerer hvis elementet har en ugjennomsiktig bakgrunn, men hva om vi har en gjennomsiktig bakgrunn? Vi skal utforske denne saken her!
Før vi begynner, la meg introdusere min gradient shadows generator. Alt du trenger å gjøre er å justere konfigurasjonen, og få koden. Men følg med fordi jeg skal hjelpe deg å forstå all logikken bak den genererte koden.
Innholdsfortegnelse
Ugjennomsiktig løsning
La oss starte med løsningen som vil fungere i 80 % av de fleste tilfeller. Det mest typiske tilfellet: du bruker et element med bakgrunn, og du må legge til en gradientskygge til det. Ingen åpenhetsproblemer å vurdere der.
Løsningen er å stole på et pseudo-element hvor gradienten er definert. Du plasserer den bak selve elementet og bruk et uskarphetfilter på den.
.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 */
}
Det ser ut som mye kode, og det er fordi det er det. Her er hvordan vi kunne ha gjort det med en box-shadow
i stedet hvis vi brukte en solid farge i stedet for en gradient.
box-shadow: 10px 8px 10px 5px orange;
Det burde gi deg en god idé om hva verdiene i den første kodebiten gjør. Vi har X- og Y-forskyvninger, uskarphetradius og spredningsavstand. Merk at vi trenger en negativ verdi for spredningsavstanden som kommer fra inset
eiendom.
Her er en demo som viser gradientskyggen ved siden av en klassiker box-shadow
:
Hvis du ser nøye etter vil du legge merke til at begge skyggene er litt forskjellige, spesielt uskarphet. Det er ikke en overraskelse fordi jeg er ganske sikker på det filter
eiendommens algoritme fungerer annerledes enn den for box-shadow
. Det er ikke en stor sak siden resultatet til syvende og sist er ganske likt.
Denne løsningen er god, men har fortsatt noen ulemper knyttet til z-index: -1
erklæring. Ja det er "stabling kontekst" skjer der!
Jeg søkte a transform
til hovedelementet, og bom! Skyggen er ikke lenger under elementet. Dette er ikke en feil, men det logiske resultatet av en stablingskontekst. Ikke bekymre deg, jeg vil ikke starte en kjedelig forklaring om stablekontekst (Jeg har allerede gjort det i en Stack Overflow-tråd), men jeg skal likevel vise deg hvordan du kan omgå det.
Den første løsningen jeg anbefaler er å bruke en 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);
}
I stedet for å bruke z-index: -1
, vil vi bruke en negativ oversettelse langs Z-aksen. Vi skal legge alt inne translate3d()
. Ikke glem å bruke transform-style: preserve-3d
på hovedelementet; ellers 3D transform
vil ikke tre i kraft.
Så vidt jeg vet, er det ingen bivirkning til denne løsningen ... men kanskje du ser en. Hvis det er tilfelle, del det i kommentarfeltet, og la oss prøve å finne en løsning på det!
Hvis du av en eller annen grunn ikke er i stand til å bruke en 3D transform
, den andre løsningen er å stole på to pseudo-elementer — ::before
og ::after
. Den ene skaper gradientskyggen, og den andre gjengir hovedbakgrunnen (og andre stiler du kanskje trenger). På den måten kan vi enkelt kontrollere stablingsrekkefølgen til begge pseudo-elementene.
.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;
}
Det er viktig å merke seg at vi er tvang hovedelementet for å skape en stablingskontekst ved å deklarere z-index: 0
eller enhver annen eiendom som gjør det samme, på den. Glem heller ikke at pseudo-elementer anser utfyllingsboksen til hovedelementet som en referanse. Så hvis hovedelementet har en kantlinje, må du ta hensyn til det når du definerer pseudoelementstilene. Du vil merke at jeg bruker inset: -2px
on ::after
for å ta hensyn til grensen definert på hovedelementet.
Som sagt er nok denne løsningen god nok i de fleste tilfeller der du ønsker en gradientskygge, så lenge du ikke trenger å støtte transparens. Men vi er her for utfordringen og for å flytte grensene, så selv om du ikke trenger det som kommer neste gang, bli med meg. Du vil sannsynligvis lære nye CSS-triks som du kan bruke andre steder.
Transparent løsning
La oss fortsette der vi slapp med 3D transform
og fjern bakgrunnen fra hovedelementet. Jeg vil starte med en skygge som har både forskyvninger og spredningsavstand lik 0
.
Tanken er å finne en måte å kutte eller skjule alt innenfor området til elementet (innenfor den grønne grensen) mens du beholder det som er utenfor. Vi skal bruke clip-path
for det. Men du lurer kanskje på hvordan clip-path
kan gjøre et kutt innsiden et element.
Det er faktisk ingen måte å gjøre det på, men vi kan simulere det ved å bruke et bestemt polygonmønster:
clip-path: polygon(-100vmax -100vmax,100vmax -100vmax,100vmax 100vmax,-100vmax 100vmax,-100vmax -100vmax,0 0,0 100%,100% 100%,100% 0,0 0)
Tada! Vi har en gradientskygge som støtter åpenhet. Alt vi gjorde var å legge til en clip-path
til forrige kode. Her er en figur for å illustrere polygondelen.
Det blå området er den synlige delen etter påføring clip-path
. Jeg bruker bare den blå fargen for å illustrere konseptet, men i virkeligheten vil vi bare se skyggen inne i det området. Som du kan se, har vi fire punkter definert med en stor verdi (B
). Min store verdi er 100vmax
, men det kan være en stor verdi du vil. Tanken er å sikre at vi har nok plass til skyggen. Vi har også fire punkter som er hjørnene til pseudo-elementet.
Pilene illustrerer banen som definerer polygonet. Vi starter fra (-B, -B)
til vi når (0,0)
. Totalt trenger vi 10 poeng. Ikke åtte punkter fordi to punkter gjentas to ganger i banen ((-B,-B)
og (0,0)
).
Det er fortsatt en ting til igjen for oss å gjøre, og det er å ta hensyn til spredningsavstanden og forskyvningene. Den eneste grunnen til at demoen ovenfor fungerer er fordi det er et spesielt tilfelle hvor forskyvninger og spredningsavstand er lik 0
.
La oss definere spredningen og se hva som skjer. Husk at vi bruker inset
med en negativ verdi for å gjøre dette:
Pseudo-elementet er nå større enn hovedelementet, så clip-path
kutter mer enn vi trenger det til. Husk at vi alltid må kutte delen innsiden hovedelementet (området innenfor den grønne kanten av eksempelet). Vi må justere plasseringen av de fire punktene på innsiden av 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)) );
}
Vi har definert en CSS-variabel, --s
, for spredningsavstanden og oppdatert polygonpunktene. Jeg rørte ikke punktene der jeg bruker den store verdien. Jeg oppdaterer bare punktene som definerer hjørnene til pseudo-elementet. Jeg øker alle nullverdiene med --s
og reduser 100%
verdier av --s
.
Det er samme logikk med offsetene. Når vi oversetter pseudoelementet, er skyggen ute av justering, og vi må rette opp polygonet igjen og flytte punktene i motsatt retning.
.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)) );
}
Det er to variabler til for offsetene: --x
og --y
. Vi bruker dem inni transform
og vi oppdaterer også clip-path
verdier. Vi berører fortsatt ikke polygonpunktene med store verdier, men vi forskyver alle de andre - vi reduserer --x
fra X-koordinatene, og --y
fra Y-koordinatene.
Nå er alt vi trenger å gjøre å oppdatere noen få variabler for å kontrollere gradientskyggen. Og mens vi er i gang, la oss også gjøre uskarphedsradius til en variabel:
Trenger vi fortsatt 3D
transform
triks?
Alt avhenger av grensen. Ikke glem at referansen for et pseudo-element er utfyllingsboksen, så hvis du bruker en kantlinje på hovedelementet ditt, vil du ha en overlapping. Du beholder enten 3D transform
lure eller oppdatere inset
verdi for å ta hensyn til grensen.
Her er den forrige demoen med en oppdatert inset
verdi i stedet for 3D transform
:
Jeg vil si at dette er en mer passende vei å gå fordi spredningsavstanden vil være mer nøyaktig, siden den starter fra kantboksen i stedet for polstringsboksen. Men du må justere inset
verdi i henhold til hovedelementets kantlinje. Noen ganger er grensen til elementet ukjent, og du må bruke den forrige løsningen.
Med den tidligere ugjennomsiktige løsningen er det mulig du vil møte et stablingskontekstproblem. Og med den gjennomsiktige løsningen er det mulig du står overfor et grenseproblem i stedet. Nå har du alternativer og måter å omgå disse problemene på. 3D-transformasjonstrikset er min favorittløsning fordi det løser alle problemene (Online-generatoren vil vurdere det også)
Legge til en kantradius
Hvis du prøver å legge til border-radius
til elementet når vi bruker den ugjennomsiktige løsningen vi startet med, er det en ganske triviell oppgave. Alt du trenger å gjøre er å arve den samme verdien fra hovedelementet, og du er ferdig.
Selv om du ikke har en kantradius, er det lurt å definere border-radius: inherit
. Det står for ethvert potensiale border-radius
du vil kanskje legge til senere eller en kantradius som kommer fra et annet sted.
Det er en annen historie når man arbeider med den gjennomsiktige løsningen. Dessverre betyr det å finne en annen løsning fordi clip-path
kan ikke håndtere krumninger. Det betyr at vi ikke vil kunne kutte området inne i hovedelementet.
Vi vil introdusere mask
eiendom til blandingen.
Denne delen var veldig kjedelig, og jeg slet med å finne en generell løsning som ikke er avhengig av magiske tall. Jeg endte opp med en veldig kompleks løsning som bare bruker ett pseudo-element, men koden var en spaghettiklump som bare dekker noen få spesielle tilfeller. Jeg tror ikke det er verdt å utforske den ruten.
Jeg bestemte meg for å sette inn et ekstra element for enklere kode. Her er markeringen:
<div class="box"> <sh></sh>
</div>
Jeg bruker et tilpasset element, <sh>
, for å unngå potensiell konflikt med ekstern CSS. Jeg kunne ha brukt en <div>
, men siden det er et vanlig element, kan det lett bli målrettet av en annen CSS-regel som kommer fra et annet sted som kan bryte koden vår.
Det første trinnet er å plassere <sh>
element og med vilje skape et overløp:
.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));
}
Koden kan se litt merkelig ut, men vi kommer til logikken bak den etter hvert. Deretter lager vi gradientskyggen ved å bruke et pseudoelement av <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);
}
Som du kan se, bruker pseudo-elementet den samme koden som alle de tidligere eksemplene. Den eneste forskjellen er 3D transform
definert på <sh>
element i stedet for pseudo-elementet. For øyeblikket har vi en gradientskygge uten gjennomsiktighetsfunksjonen:
Merk at området til <sh>
element er definert med den svarte omrisset. Hvorfor gjør jeg dette? Fordi på den måten kan jeg bruke en mask
på den for å skjule delen inne i det grønne området og holde den overfylte delen der vi trenger å se skyggen.
Jeg vet det er litt vanskelig, men ulikt clip-path
den mask
eiendom tar ikke hensyn til området utenfor et element for å vise og skjule ting. Det er derfor jeg var forpliktet til å introdusere det ekstra elementet - for å simulere "utenfor" området.
Vær også oppmerksom på at jeg bruker en kombinasjon av border
og inset
å definere dette området. Dette tillater meg å holde utfyllingsboksen til det ekstra elementet det samme som hovedelementet, slik at pseudoelementet ikke trenger ytterligere beregninger.
En annen nyttig ting vi får ut av å bruke et ekstra element er at elementet er fast, og bare pseudoelementet beveger seg (ved hjelp av translate
). Dette vil tillate meg å enkelt definere masken, som er siste trinn i dette trikset.
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
mask-composite: exclude;
Det er gjort! Vi har vår gradientskygge, og den støtter border-radius
! Du forventet sannsynligvis et kompleks mask
verdi med massevis av gradienter, men nei! Vi trenger bare to enkle gradienter og en mask-composite
for å fullføre magien.
La oss isolere <sh>
element for å forstå hva som skjer der:
.box sh { position: absolute; inset: -150px; border: 150px solid red; background: lightblue; border-radius: calc(150px + var(--r));
}
Her er hva vi får:
Legg merke til hvordan den indre radiusen samsvarer med hovedelementets border-radius
. Jeg har definert en stor grense (150px
) Og en border-radius
lik den store grensen i tillegg til hovedelementets radius. På utsiden har jeg en radius lik 150px + R
. På innsiden har jeg 150px + R - 150px = R
.
Vi må skjule den indre (blå) delen og sørge for at kanten (rød) fortsatt er synlig. For å gjøre det har jeg definert to maskelag - Ett som bare dekker innholdsboksområdet og et annet som dekker kantfeltområdet (standardverdien). Så ekskluderte jeg den ene fra den andre for å avsløre grensen.
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
mask-composite: exclude;
Jeg brukte samme teknikk til lage en kantlinje som støtter gradienter og border-radius
. Ana Tudor har også en god artikkel om maskeringskompositt som jeg inviterer deg til å lese.
Er det noen ulemper med denne metoden?
Ja, dette er definitivt ikke perfekt. Det første problemet du kan møte er relatert til bruk av en kantlinje på hovedelementet. Dette kan skape en liten feiljustering i radiene hvis du ikke tar hensyn til det. Vi har dette problemet i vårt eksempel, men kanskje du nesten ikke legger merke til det.
Løsningen er relativt enkel: Legg til kantens bredde for <sh>
elementets 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));
}
En annen ulempe er den store verdien vi bruker for grensen (150px
i eksemplet). Denne verdien bør være stor nok til å inneholde skyggen, men ikke for stor for å unngå problemer med overløp og rullefelt. Heldigvis, nettgeneratoren vil beregne den optimale verdien med tanke på alle parameterne.
Den siste ulempen jeg er klar over er når du jobber med et kompleks border-radius
. For eksempel, hvis du vil ha en annen radius på hvert hjørne, må du definere en variabel for hver side. Det er egentlig ikke en ulempe, antar jeg, men det kan gjøre koden din litt vanskeligere å vedlikeholde.
.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);
}
Online-generatoren vurderer kun en enhetlig radius for enkelhets skyld, men du vet nå hvordan du endrer koden hvis du vil vurdere en kompleks radiuskonfigurasjon.
Innpakning opp
Vi har nådd slutten! Magien bak gradientskygger er ikke lenger et mysterium. Jeg prøvde å dekke alle mulighetene og eventuelle problemer du måtte møte. Hvis jeg gikk glipp av noe eller du oppdager et problem, kan du gjerne rapportere det i kommentarfeltet, så skal jeg sjekke det ut.
Igjen, mye av dette er sannsynligvis overdreven med tanke på at de facto-løsningen vil dekke de fleste brukstilfellene dine. Likevel er det godt å vite "hvorfor" og "hvordan" bak trikset, og hvordan man kan overvinne dets begrensninger. I tillegg fikk vi god øvelse ved å leke med CSS-klipping og maskering.
Og det har du selvfølgelig nettgeneratoren du kan nå når som helst du vil unngå bryet.
- SEO-drevet innhold og PR-distribusjon. Bli forsterket i dag.
- Platoblokkkjede. Web3 Metaverse Intelligence. Kunnskap forsterket. Tilgang her.
- kilde: https://css-tricks.com/different-ways-to-get-css-gradient-shadows/
- 1
- 10
- 11
- 3d
- 7
- 9
- 98
- a
- I stand
- Om oss
- om det
- ovenfor
- Absolute
- Ifølge
- Logg inn
- kontoer
- nøyaktig
- faktisk
- Ytterligere
- Etter
- algoritme
- Alle
- tillater
- allerede
- alltid
- Ann
- og
- En annen
- hvor som helst
- anvendt
- Påfør
- påføring
- AREA
- rundt
- Artikkel
- bakgrunn
- I utgangspunktet
- fordi
- før du
- bak
- tro
- under
- Stor
- større
- Bit
- Svart
- Blogg
- Blå
- uskarphet
- grensen
- Kjedelig
- Eske
- Break
- Bug
- beregne
- beregninger
- kan ikke
- saken
- saker
- utfordre
- sjekk
- Classic
- klippebane
- tett
- kode
- farge
- kombinasjon
- kommer
- kommentere
- Felles
- fullføre
- komplekse
- konsept
- Konfigurasjon
- konflikt
- Vurder
- vurderer
- anser
- innhold
- kontekst
- kontroll
- Corner
- hjørner
- kunne
- Kurs
- dekke
- Dekker
- skape
- skaper
- CSS
- CSS triks
- skikk
- Kutt
- kutt
- avtale
- håndtering
- besluttet
- redusere
- Misligholde
- definert
- definerer
- definere
- helt sikkert
- avhenger
- gJORDE
- forskjell
- forskjellig
- retning
- oppdage
- avstand
- ikke
- gjør
- ikke
- ulemper
- hver enkelt
- Tidligere
- lett
- effekt
- enten
- andre steder
- nok
- sikre
- spesielt
- Selv
- alt
- eksempel
- eksempler
- ekskludert
- Øvelse
- forventet
- forklaring
- utforske
- Utforske
- utvendig
- ekstra
- Face
- ganske
- Favoritt
- Trekk
- Noen få
- Figur
- filtrere
- Finn
- finne
- Først
- Fix
- fikset
- følge
- Tving
- Gratis
- fra
- general
- generert
- få
- Gi
- Go
- skal
- god
- gradienter
- Grønn
- skjer
- hjelpe
- her.
- Gjemme seg
- Hvordan
- Hvordan
- HTML
- HTTPS
- JEG VIL
- Tanken
- viktig
- in
- Øke
- i stedet
- introdusere
- invitere
- utstedelse
- saker
- IT
- Hold
- holde
- Vet
- Siste
- lag
- LÆRE
- Sannsynlig
- begrensninger
- grenser
- lite
- logisk
- Lang
- lenger
- Se
- så
- UTSEENDE
- Lot
- magi
- Hoved
- vedlikeholde
- Flertall
- gjøre
- maske
- midler
- metode
- kunne
- modifisere
- øyeblikk
- mer
- mest
- flytte
- flytting
- Mozilla
- Mystery
- Trenger
- negativ
- likevel
- Ny
- neste
- offset
- ONE
- på nett
- motsatt
- optimal
- alternativer
- oransje
- rekkefølge
- Annen
- andre
- ellers
- omriss
- utenfor
- Overcome
- parametere
- del
- Spesielt
- banen
- Mønster
- perfekt
- kanskje
- plukke
- Sted
- plato
- Platon Data Intelligence
- PlatonData
- spiller
- vær så snill
- i tillegg til
- poeng
- polygon
- posisjon
- muligheter
- mulig
- Post
- potensiell
- pen
- forrige
- sannsynligvis
- eiendom
- Skyv
- sette
- spørsmål
- å nå
- nådd
- Lese
- Reality
- grunnen til
- anbefaler
- Rød
- redusere
- i slekt
- relativt
- husker
- fjerne
- gjentatt
- rapporterer
- resultere
- avsløre
- Rute
- Regel
- Sa
- sake
- samme
- Seksjon
- Shadow
- Del
- bør
- Vis
- side
- lignende
- Enkelt
- enkelhet
- siden
- liten
- So
- solid
- løsning
- noen
- noe
- et sted
- Rom
- spesifikk
- spre
- stable
- stabling
- Begynn
- startet
- starter
- opphold
- Trinn
- Still
- Story
- egnet
- støtte
- Støtter
- overraskelse
- Ta
- målrettet
- Oppgave
- De
- Området
- ting
- ting
- til
- sammen
- også
- Tema
- Totalt
- berøre
- Transform
- oversette
- Oversettelse
- Åpenhet
- gjennomsiktig
- sant
- typisk
- forstå
- Oppdater
- oppdatert
- us
- bruke
- verdi
- Verdier
- synlig
- måter
- Hva
- Hva er
- hvilken
- mens
- vil
- uten
- Arbeid
- arbeid
- virker
- verdt
- X
- Du
- Din
- z-indeks
- zephyrnet
- null