Det er et spørgsmål, jeg ofte hører stillet: Er det muligt at skabe skygger fra gradienter i stedet for solide farver? Der er ingen specifik CSS-egenskab, der gør dette (tro mig, jeg har kigget), og ethvert blogindlæg, du finder om det, er dybest set en masse CSS-tricks til at tilnærme en gradient. Vi vil faktisk dække nogle af dem, mens vi går.
Men først… en artikel om gradientskygger? Virkelig?
Ja, dette er endnu et indlæg om emnet, men det er anderledes. Sammen vil vi skubbe grænserne for at få en løsning, der dækker noget, jeg ikke har set andre steder: gennemsigtighed. De fleste tricks virker, hvis elementet har en ikke-gennemsigtig baggrund, men hvad hvis vi har en gennemsigtig baggrund? Vi vil undersøge denne sag her!
Inden vi starter, lad mig introducere min gradient shadows generator. Alt du skal gøre er at justere konfigurationen og få koden. Men følg med, fordi jeg vil hjælpe dig med at forstå al logikken bag den genererede kode.
Indholdsfortegnelse
Ugennemsigtig løsning
Lad os starte med den løsning, der vil fungere i 80 % af de fleste tilfælde. Det mest typiske tilfælde: du bruger et element med en baggrund, og du skal tilføje en gradientskygge til det. Ingen gennemsigtighedsproblemer at overveje der.
Løsningen er at stole på et pseudo-element, hvor gradienten er defineret. Du placerer det bag selve elementet og påfør et sløringsfilter på det.
.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 ligner meget kode, og det er fordi det er det. Her er hvordan vi kunne have gjort det med en box-shadow
i stedet hvis vi brugte en ensfarvet i stedet for en gradient.
box-shadow: 10px 8px 10px 5px orange;
Det burde give dig en god idé om, hvad værdierne i det første uddrag gør. Vi har X- og Y-forskydninger, sløringsradius og spredningsafstanden. Bemærk, at vi har brug for en negativ værdi for spredningsafstanden, der kommer fra inset
ejendom.
Her er en demo, der viser gradientskyggen ved siden af en klassiker box-shadow
:
Hvis du ser godt efter, vil du bemærke, at begge skygger er lidt forskellige, især sløringsdelen. Det er ikke en overraskelse, fordi jeg er ret sikker på filter
ejendommens algoritme virker anderledes end den til box-shadow
. Det er ikke en stor sag, da resultatet i sidste ende er ret ens.
Denne løsning er god, men har stadig et par ulemper relateret til z-index: -1
erklæring. Ja der er "stabling kontekst" sker der!
Jeg ansøgte en transform
til hovedelementet, og boom! Skyggen er ikke længere under elementet. Dette er ikke en fejl, men det logiske resultat af en stablingskontekst. Bare rolig, jeg vil ikke starte en kedelig forklaring om stacking kontekst (Det har jeg allerede gjort i en Stack Overflow-tråd), men jeg vil stadig vise dig, hvordan du kan omgå det.
Den første løsning, som jeg anbefaler, er at bruge 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 at bruge z-index: -1
, vil vi bruge en negativ oversættelse langs Z-aksen. Vi lægger alt indenfor translate3d()
. Glem ikke at bruge transform-style: preserve-3d
på hovedelementet; ellers 3D transform
træder ikke i kraft.
Så vidt jeg ved, er der ingen bivirkning ved denne løsning ... men måske du ser en. Hvis det er tilfældet, så del det i kommentarfeltet, og lad os prøve at finde en løsning på det!
Hvis du af en eller anden grund ikke er i stand til at bruge en 3D transform
, den anden løsning er at stole på to pseudo-elementer — ::before
, ::after
. Den ene skaber gradientskyggen, og den anden gengiver hovedbaggrunden (og andre stilarter, du måske har brug for). På den måde kan vi nemt styre stablerækkefølgen af begge pseudo-elementer.
.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 vigtigt at bemærke, at vi er tvinger hovedelementet til at skabe en stablingskontekst ved at deklarere z-index: 0
eller enhver anden ejendom, der gør det samme, på det. Glem heller ikke, at pseudo-elementer betragter hovedelementets polstringsboks som en reference. Så hvis hovedelementet har en kant, skal du tage det i betragtning, når du definerer pseudoelement-stilene. Du vil bemærke, at jeg bruger inset: -2px
on ::after
at tage højde for grænsen defineret på hovedelementet.
Som sagt er denne løsning nok god nok i de fleste tilfælde, hvor du ønsker en gradient-skygge, så længe du ikke skal understøtte transparens. Men vi er her for udfordringen og for at skubbe grænserne, så selvom du ikke har brug for det, der kommer, så bliv hos mig. Du vil sikkert lære nye CSS-tricks, som du kan bruge andre steder.
Gennemsigtig løsning
Lad os fortsætte, hvor vi slap med 3D transform
og fjern baggrunden fra hovedelementet. Jeg vil starte med en skygge, der både har forskydninger og spredningsafstand lig med 0
.
Ideen er at finde en måde at skære eller skjule alt inde i elementets område (inde i den grønne kant), mens man beholder det, der er udenfor. Vi skal bruge clip-path
for det. Men du kan undre dig over hvordan clip-path
kan lave et snit indvendig et element.
Der er faktisk ingen måde at gøre det på, men vi kan simulere det ved hjælp af 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, der understøtter gennemsigtighed. Det eneste, vi gjorde, var at tilføje en clip-path
til den forrige kode. Her er en figur til at illustrere polygondelen.
Det blå område er den synlige del efter påføring clip-path
. Jeg bruger kun den blå farve til at illustrere konceptet, men i virkeligheden vil vi kun se skyggen inde i det område. Som du kan se, har vi fire punkter defineret med en stor værdi (B
). Min store værdi er 100vmax
, men det kan være enhver stor værdi, du ønsker. Tanken er at sikre, at vi har plads nok til skyggen. Vi har også fire punkter, der er hjørnerne af pseudo-elementet.
Pilene illustrerer stien, der definerer polygonen. Vi starter fra (-B, -B)
indtil vi når (0,0)
. I alt skal vi bruge 10 point. Ikke otte punkter, fordi to punkter gentages to gange i stien ((-B,-B)
, (0,0)
).
Der er stadig en ting mere tilbage for os at gøre, og det er at tage højde for spredningsafstanden og forskydningerne. Den eneste grund til, at demoen ovenfor virker, er fordi det er et særligt tilfælde, hvor offset og spredningsafstand er lig med 0
.
Lad os definere spredningen og se, hvad der sker. Husk at vi bruger inset
med en negativ værdi for at gøre dette:
Pseudo-elementet er nu større end hovedelementet, så clip-path
skærer mere end vi har brug for. Husk, at vi altid skal skære delen af indvendig hovedelementet (området inden for eksemplets grønne kant). Vi skal justere placeringen af de fire punkter inde i 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 defineret en CSS-variabel, --s
, for spredningsafstanden og opdaterede polygonpunkterne. Jeg rørte ikke ved de punkter, hvor jeg bruger den store værdi. Jeg opdaterer kun de punkter, der definerer hjørnerne af pseudo-elementet. Jeg øger alle nulværdierne med --s
og formindsk 100%
værdier ved --s
.
Det er den samme logik med forskydningerne. Når vi oversætter pseudo-elementet, er skyggen ude af justering, og vi skal rette polygonen igen og flytte punkterne i den modsatte 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)) );
}
Der er yderligere to variabler for forskydningerne: --x
, --y
. Vi bruger dem indeni transform
og vi opdaterer også clip-path
værdier. Vi rører stadig ikke polygonpunkterne med store værdier, men vi forskyder alle de andre - vi reducerer --x
fra X-koordinaterne, og --y
fra Y-koordinaterne.
Nu skal vi bare opdatere nogle få variabler for at kontrollere gradientskyggen. Og mens vi er i gang, lad os også gøre sløringsradius til en variabel:
Har vi stadig brug for 3D
transform
trick?
Det hele afhænger af grænsen. Glem ikke, at referencen for et pseudo-element er polstringsboksen, så hvis du anvender en kant til dit hovedelement, vil du have et overlap. Du beholder enten 3D transform
trick eller opdater inset
værdi for at tage højde for grænsen.
Her er den tidligere demo med en opdateret inset
værdi i stedet for 3D transform
:
Jeg vil sige, at dette er en mere passende måde at gå, fordi spredningsafstanden vil være mere nøjagtig, da den starter fra kant-boksen i stedet for polstring-boksen. Men du bliver nødt til at justere inset
værdi i henhold til hovedelementets grænse. Nogle gange er grænsen for elementet ukendt, og du skal bruge den tidligere løsning.
Med den tidligere ikke-gennemsigtige løsning er det muligt, at du står over for et stablingskontekstproblem. Og med den gennemsigtige løsning er det muligt, at du i stedet står over for et grænseproblem. Nu har du muligheder og måder at løse disse problemer på. 3D transformation tricket er min favorit løsning, fordi det løser alle problemer (Online-generatoren vil også overveje det)
Tilføjelse af en kantradius
Hvis du prøver at tilføje border-radius
til elementet ved brug af den ugennemsigtige løsning, vi startede med, er det en ret triviel opgave. Alt du skal gøre er at arve den samme værdi fra hovedelementet, og du er færdig.
Selvom du ikke har en kantradius, er det en god idé at definere border-radius: inherit
. Det tegner sig for ethvert potentiale border-radius
du vil måske tilføje senere eller en kantradius, der kommer fra et andet sted.
Det er en anden historie, når man beskæftiger sig med den gennemsigtige løsning. Desværre betyder det at finde en anden løsning pga clip-path
kan ikke håndtere krumninger. Det betyder, at vi ikke vil være i stand til at skære området inde i hovedelementet.
Vi vil introducere mask
ejendom til blandingen.
Denne del var meget kedelig, og jeg kæmpede for at finde en generel løsning, der ikke er afhængig af magiske tal. Jeg endte med en meget kompleks løsning, der kun bruger ét pseudo-element, men koden var en klump spaghetti, der kun dækker nogle få særlige tilfælde. Jeg tror ikke, det er værd at udforske den rute.
Jeg besluttede at indsætte et ekstra element for en enklere kodes skyld. Her er markeringen:
<div class="box"> <sh></sh>
</div>
Jeg bruger et brugerdefineret element, <sh>
, for at undgå enhver potentiel konflikt med ekstern CSS. Jeg kunne have brugt en <div>
, men da det er et almindeligt element, kan det nemt blive målrettet af en anden CSS-regel, der kommer fra et andet sted, som kan bryde vores kode.
Det første skridt er at placere <sh>
element og bevidst skabe et overløb:
.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 ser måske lidt mærkelig ud, men vi kommer til logikken bag den, mens vi går. Dernæst opretter vi gradientskyggen ved hjælp af et pseudo-element af <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, bruger pseudo-elementet den samme kode som alle de foregående eksempler. Den eneste forskel er 3D transform
defineret på <sh>
element i stedet for pseudo-elementet. I øjeblikket har vi en gradientskygge uden gennemsigtighedsfunktionen:
Bemærk, at området af <sh>
element er defineret med den sorte omrids. Hvorfor gør jeg dette? For på den måde er jeg i stand til at anvende en mask
på den for at skjule delen inde i det grønne område og holde den overfyldte del, hvor vi skal se skyggen.
Jeg ved godt det er lidt tricky, men i modsætning til clip-path
, mask
ejendom tager ikke højde for området uden for et element til at vise og skjule ting. Det er derfor, jeg var forpligtet til at introducere det ekstra element - at simulere det "ydre" område.
Bemærk også, at jeg bruger en kombination af border
, inset
at definere det område. Dette giver mig mulighed for at holde polstringsboksen for det ekstra element den samme som hovedelementet, så pseudoelementet ikke behøver yderligere beregninger.
En anden nyttig ting, vi får ved at bruge et ekstra element, er, at elementet er fast, og kun pseudo-elementet bevæger sig (vha. translate
). Dette vil give mig mulighed for nemt at definere masken, som er sidste trin i dette trick.
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
mask-composite: exclude;
Det er gjort! Vi har vores gradientskygge, og den understøtter border-radius
! Du forventede sikkert et kompleks mask
værdi med masser af gradienter, men nej! Vi har kun brug for to simple gradienter og en mask-composite
for at fuldende magien.
Lad os isolere <sh>
element for at forstå, hvad der sker der:
.box sh { position: absolute; inset: -150px; border: 150px solid red; background: lightblue; border-radius: calc(150px + var(--r));
}
Her er hvad vi får:
Bemærk, hvordan den indre radius matcher hovedelementets border-radius
. Jeg har defineret en stor grænse (150px
) Og en border-radius
lig med den store grænse plus hovedelementets radius. På ydersiden har jeg en radius svarende til 150px + R
. På indersiden har jeg 150px + R - 150px = R
.
Vi skal skjule den indre (blå) del og sørge for, at kanten (rød) stadig er synlig. For at gøre det har jeg defineret to maskelag - Et, der kun dækker indholdsboksområdet og et andet, der dækker kantfeltets område (standardværdien). Så udelukkede jeg den ene fra den anden for at afsløre grænsen.
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
mask-composite: exclude;
Jeg brugte samme teknik til skabe en kant, der understøtter gradienter og border-radius
. Ana Tudor har også en god artikel om maskeringskomposit som jeg inviterer dig til at læse.
Er der nogen ulemper ved denne metode?
Ja, det her er bestemt ikke perfekt. Det første problem, du kan støde på, er relateret til at bruge en kant på hovedelementet. Dette kan skabe en lille forskydning i radierne, hvis du ikke tager højde for det. Vi har dette problem i vores eksempel, men måske kan du næsten ikke bemærke det.
Rettelsen er forholdsvis nem: Tilføj 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 anden ulempe er den store værdi, vi bruger til grænsen (150px
i eksemplet). Denne værdi skal være stor nok til at indeholde skyggen, men ikke for stor for at undgå problemer med overløb og rullepanel. Heldigvis, online-generatoren vil beregne den optimale værdi i betragtning af alle parametrene.
Den sidste ulempe, jeg er klar over, er, når du arbejder med et kompleks border-radius
. For eksempel, hvis du vil have en anden radius på hvert hjørne, skal du definere en variabel for hver side. Det er egentlig ikke en ulempe, formoder jeg, men det kan gøre din kode lidt sværere at vedligeholde.
.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 overvejer kun en ensartet radius for overskuelighedens skyld, men du ved nu, hvordan du ændrer koden, hvis du vil overveje en kompleks radiuskonfiguration.
Indpakning op
Vi er nået til slutningen! Magien bag gradientskygger er ikke længere et mysterium. Jeg forsøgte at dække alle de muligheder og eventuelle problemer, du måtte stå over for. Hvis jeg er gået glip af noget, eller du opdager et problem, er du velkommen til at rapportere det i kommentarfeltet, så tjekker jeg det ud.
Igen, meget af dette er sandsynligvis overkill i betragtning af, at de facto-løsningen vil dække de fleste af dine brugssager. Ikke desto mindre er det godt at vide "hvorfor" og "hvordan" bag tricket, og hvordan man overvinder dets begrænsninger. Derudover fik vi god øvelse i at lege med CSS-klipning og maskering.
Og det har du selvfølgelig online-generatoren du kan nå når som helst du vil undgå besværet.
- SEO Powered Content & PR Distribution. Bliv forstærket i dag.
- Platoblokkæde. Web3 Metaverse Intelligence. Viden forstærket. Adgang her.
- Kilde: https://css-tricks.com/different-ways-to-get-css-gradient-shadows/
- 1
- 10
- 11
- 3d
- 7
- 9
- 98
- a
- I stand
- Om
- om det
- over
- absolutte
- Ifølge
- Konto
- Konti
- præcis
- faktisk
- Yderligere
- Efter
- algoritme
- Alle
- tillader
- allerede
- altid
- Ana
- ,
- En anden
- overalt
- anvendt
- Indløs
- Anvendelse
- OMRÅDE
- omkring
- artikel
- undgå
- baggrund
- I bund og grund
- fordi
- før
- bag
- Tro
- jf. nedenstående
- Big
- større
- Bit
- Sort
- Blog
- Blå
- sløring
- grænse
- Boring
- Boks
- Pause
- Bug
- beregne
- kan ikke
- tilfælde
- tilfælde
- udfordre
- kontrollere
- Classic
- klippesti
- nøje
- kode
- farve
- kombination
- kommer
- KOMMENTAR
- Fælles
- fuldføre
- komplekse
- Konceptet
- Konfiguration
- konflikt
- Overvej
- Overvejer
- anser
- indhold
- sammenhæng
- kontrol
- Corner
- hjørner
- kunne
- kursus
- dæksel
- dækker
- skabe
- skaber
- CSS
- CSS-tricks
- skik
- Klip
- nedskæringer
- deal
- beskæftiger
- besluttede
- falde
- Standard
- definerede
- definerer
- definere
- definitivt
- Demo
- afhænger
- DID
- forskel
- forskellige
- retning
- opdage
- afstand
- Er ikke
- gør
- Dont
- ulemper
- hver
- tidligere
- nemt
- effekt
- enten
- andetsteds
- nok
- sikre
- især
- Ether (ETH)
- Endog
- at alt
- eksempel
- eksempler
- udelukket
- Dyrke motion
- forventet
- forklaring
- udforske
- Udforskning
- ekstern
- ekstra
- Ansigtet
- facto
- retfærdigt
- Favorit
- Feature
- få
- Figur
- filtrere
- Finde
- finde
- Fornavn
- Fix
- fast
- følger
- Tving
- Gratis
- fra
- Generelt
- genereret
- få
- Giv
- Go
- gå
- godt
- gradienter
- Grøn
- sker
- hjælpe
- link.
- Skjule
- Hvordan
- How To
- HTML
- HTTPS
- SYG
- idé
- vigtigt
- in
- Forøg
- i stedet
- indføre
- invitere
- spørgsmål
- spørgsmål
- IT
- Holde
- holde
- Kend
- Efternavn
- lag
- LÆR
- Sandsynlig
- begrænsninger
- grænser
- lidt
- Lang
- længere
- Se
- kiggede
- UDSEENDE
- Lot
- Magic
- Main
- vedligeholde
- Flertal
- lave
- maske
- midler
- metode
- måske
- ændre
- øjeblik
- mere
- mest
- bevæge sig
- flytning
- Mozilla
- Mystery
- Behov
- negativ
- Ikke desto mindre
- Ny
- næste
- offset
- ONE
- online
- modsat
- optimal
- Indstillinger
- Orange
- ordrer
- Andet
- Andre
- Ellers
- skitse
- uden for
- Overvind
- parametre
- del
- særlig
- sti
- Mønster
- perfekt
- måske
- pick
- Place
- plato
- Platon Data Intelligence
- PlatoData
- spiller
- Vær venlig
- plus
- punkter
- Polygon
- position
- muligheder
- mulig
- Indlæg
- potentiale
- smuk
- tidligere
- sandsynligvis
- ejendom
- Skub ud
- sætte
- spørgsmål
- nå
- nået
- Læs
- Reality
- grund
- anbefaler
- Rød
- reducere
- relaterede
- relativt
- stole
- huske
- Fjern
- gentaget
- indberette
- resultere
- afsløre
- R
- Herske
- Said
- skyld
- samme
- Sektion
- Shadow
- Del
- bør
- Vis
- lignende
- Simpelt
- enkelhed
- siden
- lille
- So
- solid
- løsninger
- nogle
- noget
- et eller andet sted
- Space
- specifikke
- spredes
- stable
- stabling
- starte
- påbegyndt
- starter
- forblive
- Trin
- Stadig
- Story
- egnede
- support
- Understøtter
- overraskelse
- Tag
- målrettet
- Opgaver
- Området
- ting
- ting
- Tænk
- til
- sammen
- også
- emne
- I alt
- Transform
- Oversætte
- Oversættelse
- Gennemsigtighed
- gennemsigtig
- tricks
- sand
- typisk
- forstå
- Opdatering
- opdateret
- us
- brug
- værdi
- Værdier
- synlig
- måder
- Hvad
- Hvad er
- som
- mens
- vilje
- uden
- Arbejde
- arbejder
- virker
- værd
- X
- Din
- z-index
- zephyrnet
- nul