Wolfenstein, der kun er CSS er et lille projekt, som jeg lavede for et par uger siden. Det var et eksperiment med CSS 3D-transformationer og animationer.
Inspireret af FPS demo og en anden Wolfenstein CodePen, besluttede jeg at bygge min egen version. Det er løst baseret på Episode 1 – Etage 9 af det originale Wolfenstein 3D-spil.
Redaktør: Dette spil kræver med vilje en hurtig reaktion for at undgå en Game Over-skærm.
Her er en playthrough-video:
I en nøddeskal er mit projekt intet andet end en omhyggeligt scriptet lang CSS-animation. Plus et par forekomster af afkrydsningsfelt hack.
:checked ~ div { animation-name: spin; }
Miljøet består af 3D-gitterflader, og animationerne er for det meste almindelige 3D-oversættelser og -rotationer. Intet rigtig fancy.
To problemer var dog særligt vanskelige at løse:
- Spil "våbenaffyring"-animationen, når spilleren klikker på en fjende.
- Når den hurtige chef fik det sidste hit, indtast en dramatisk slowmotion.
På teknisk niveau betød dette:
- Afspil en animation igen, når det næste afkrydsningsfelt er markeret.
- Sæt farten ned for en animation, når et afkrydsningsfelt er markeret.
Faktisk blev ingen af dem løst ordentligt i mit projekt! Jeg endte enten med at bruge løsninger eller gav bare op.
På den anden side, efter lidt gravearbejde, fandt jeg til sidst nøglen til begge problemer: at ændre egenskaberne ved at køre CSS-animationer. I denne artikel vil vi udforske dette emne yderligere:
- Masser af interaktive eksempler.
- Dissektioner: hvordan fungerer hvert eksempel (eller virker ikke)?
- Bag scenen: hvordan håndterer browsere animationstilstande?
Lad mig "smid mine klodser".
Problem 1: Genafspilning af animation
Det første eksempel: "bare endnu et afkrydsningsfelt"
Min første intuition var "bare tilføje endnu et afkrydsningsfelt", som ikke virker:
Hvert afkrydsningsfelt fungerer individuelt, men ikke begge sammen. Hvis et afkrydsningsfelt allerede er markeret, virker det andet ikke længere.
Sådan fungerer det (eller "virker ikke"):
-
animation-name
of<div>
isnone
som standard. - Brugeren klikker på et afkrydsningsfelt,
animation-name
bliverspin
, og animationen starter fra begyndelsen. - Efter et stykke tid klikker brugeren på det andet afkrydsningsfelt. En ny CSS-regel træder i kraft, men
animation-name
is stadigspin
, hvilket betyder, at ingen animation tilføjes eller fjernes. Animationen fortsætter simpelthen som om intet var hændt.
Det andet eksempel: "kloning af animationen"
En arbejdsmetode er at klone animationen:
#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2; }
Her er hvordan det virker:
animation-name
isnone
i første omgang.- Brugeren klikker på "Spin!",
animation-name
bliverspin1
. Animationenspin1
er startet fra begyndelsen, fordi den lige er blevet tilføjet. - Brugeren klikker på "Spin igen!",
animation-name
bliverspin2
. Animationenspin2
er startet fra begyndelsen, fordi den lige er blevet tilføjet.
Bemærk, at i trin #3, spin1
er fjernet på grund af rækkefølgen af CSS-reglerne. Det virker ikke, hvis "Spin igen!" kontrolleres først.
Det tredje eksempel: "tilføj den samme animation"
En anden arbejdsmetode er at "tilføje den samme animation":
#spin1:checked ~ div { animation-name: spin; }
#spin2:checked ~ div { animation-name: spin, spin; }
Dette svarer til det foregående eksempel. Du kan faktisk forstå adfærden på denne måde:
#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2, spin1; }
Bemærk, at når "Snurr igen!" er markeret, bliver den gamle kørende animation den anden animation på den nye liste, hvilket kan være uintuitivt. En direkte konsekvens er: tricket virker ikke hvis animation-fill-mode
is forwards
. Her er en demo:
Hvis du undrer dig over, hvorfor dette er tilfældet, er her nogle spor:
animation-fill-mode
isnone
som standard, hvilket betyder "Animationen har overhovedet ingen effekt, hvis den ikke afspilles".animation-fill-mode: forwards;
betyder "Når animationen er færdig med at spille, skal den forblive ved den sidste keyframe for evigt".spin1
's beslutning altid tilsidesættespin2
skyldes detspin1
vises senere på listen.- Antag, at brugeren klikker på "Spin!", venter på et helt spin, og klikker derefter på "Spin igen!". I dette øjeblik.
spin1
er allerede færdig, ogspin2
bare starter.
Diskussion
Tommelfingerregel: du kan ikke "genstarte" en eksisterende CSS-animation. I stedet vil du tilføje og afspille en ny animation. Dette kan bekræftes af W3C-specifikationen:
Når en animation er startet, fortsætter den, indtil den slutter, eller animationsnavnet fjernes.
Når man nu sammenligner de sidste to eksempler, tror jeg i praksis, at "kloning af animationer" ofte burde fungere bedre, især når CSS-preprocessor er tilgængelig.
Opgave 2: Slowmotion
Man kan tro, at det at bremse en animation bare er et spørgsmål om at indstille en længere animation-duration
:
div { animation-duration: 0.5s; }
#slowmo:checked ~ div { animation-duration: 1.5s; }
Dette virker faktisk:
… eller gør det?
Med et par justeringer burde det være lettere at se problemet.
Ja, animationen er bremset. Og nej, det ser ikke godt ud. Hunden "hopper" (næsten) altid, når du skifter afkrydsningsfeltet. Desuden ser hunden ud til at hoppe til en tilfældig position i stedet for den oprindelige. Hvorfor?
Det ville være lettere at forstå det, hvis vi introducerede to "skyggeelementer":
Begge skyggeelementer kører de samme animationer med forskellige animation-duration
. Og de påvirkes ikke af afkrydsningsfeltet.
Når du skifter afkrydsningsfeltet, skifter elementet bare øjeblikkeligt mellem tilstandene for to skyggeelementer.
Citerer W3C-specifikationen:
Ændringer af værdierne for animationsegenskaber, mens animationen kører, gælder, som om animationen havde disse værdier, fra da den startede.
Dette følger den statsløs design, som giver browsere mulighed for nemt at bestemme den animerede værdi. Selve beregningen er beskrevet link. , link..
Endnu et Forsøg
En idé er at sætte den aktuelle animation på pause og derefter tilføje en langsommere animation, der tager over derfra:
div {
animation-name: spin1;
animation-duration: 2s;
}
#slowmo:checked ~ div {
animation-name: spin1, spin2;
animation-duration: 2s, 5s;
animation-play-state: paused, running;
}
Så det virker:
… eller gør det?
Det går langsommere, når du klikker på "Slowmo!". Men hvis du venter på en fuld cirkel, vil du se et "hop". Faktisk hopper den altid til positionen, når "Slowmo!" er klikket på.
Grunden er, at vi ikke har en from
keyframe defineret – og det burde vi ikke. Når brugeren klikker på "Slowmo!", spin1
er sat på pause i en eller anden position, og spin2
starter i nøjagtig samme position. Vi kan simpelthen ikke forudsige den holdning på forhånd … eller kan vi?
En fungerende løsning
Vi kan! Ved at bruge en brugerdefineret egenskab kan vi fange vinklen i den første animation og derefter overføre den til den anden animation:
div {
transform: rotate(var(--angle1));
animation-name: spin1;
animation-duration: 2s;
}
#slowmo:checked ~ div {
transform: rotate(var(--angle2));
animation-name: spin1, spin2;
animation-duration: 2s, 5s;
animation-play-state: paused, running;
}
@keyframes spin1 {
to {
--angle1: 360deg;
}
}
@keyframes spin2 {
from {
--angle2: var(--angle1);
}
to {
--angle2: calc(var(--angle1) + 360deg);
}
}
Bemærk: @property
bruges i dette eksempel, dvs understøttes ikke af alle browsere.
Den "perfekte" løsning
Der er en advarsel til den tidligere løsning: "at afslutte slowmo" fungerer ikke godt.
Her er en bedre løsning:
I denne version kan slowmotion aktiveres eller afsluttes problemfrit. Der bruges heller ikke nogen eksperimentel funktion. Så er det den perfekte løsning? Ja og nej.
Denne løsning fungerer som at "skifte" "gear":
- Gear: der er to
<div>
s. Den ene er forælder til den anden. Begge harspin
animation, men med anderledesanimation-duration
. Den endelige tilstand af elementet er akkumuleringen af begge animationer. - Skift: I begyndelsen kun én
<div>
har sin animation kørende. Den anden er sat på pause. Når afkrydsningsfeltet er slået til/fra, skifter begge animationer deres tilstand.
Selvom jeg virkelig godt kan lide resultatet, er der et problem: det er en god udnyttelse af spin
animation, som ikke virker til andre typer animationer generelt.
En praktisk løsning (med JS)
For generelle animationer er det muligt at opnå slowmotion-funktionen med en smule JavaScript:
En hurtig forklaring:
- En tilpasset egenskab bruges til at spore animationens fremskridt.
- Animationen "genstartes", når afkrydsningsfeltet er til/fra.
- JS-koden beregner det rigtige
animation-delay
for at sikre en problemfri overgang. jeg anbefaler denne artikel hvis du ikke er bekendt med negative værdier afanimation-delay
.
Du kan se denne løsning som en hybrid af "genstart af animation" og "gearskifte"-tilgangen.
Her er det vigtigt at spore animationens fremskridt korrekt. Løsninger er mulige, hvis @property
er ikke tilgængelig. Som et eksempel bruger denne version z-index
for at spore fremskridt:
Sidenote: oprindeligt prøvede jeg også at oprette en CSS-only version, men det lykkedes ikke. Selvom jeg ikke er 100% sikker, tror jeg det er fordi animation-delay
is ikke animerbar.
Her er en version med minimal JavaScript. Kun "at gå ind i slowmo" virker.
Giv mig venligst besked, hvis du formår at oprette en fungerende CSS-only version!
Slow-mo enhver animation (med JS)
Til sidst vil jeg gerne dele en løsning, der fungerer til (næsten) enhver animation, selv med flere komplicerede @keyframes
:
Dybest set skal du tilføje en animationsfremskridtsmåler og derefter omhyggeligt beregne animation-delay
til den nye animation. Nogle gange kan det dog være vanskeligt (men muligt) at få de korrekte værdier.
For eksempel:
animation-timing-function
er ikkelinear
.animation-direction
er ikkenormal
.- flere værdier i
animation-name
med forskelliganimation-duration
s oganimation-delay
'S.
Denne metode er også beskrevet link. for Web Animations API.
Tak
Jeg startede ned ad denne vej efter at have stødt på CSS-only-projekter. Nogle var delikat kunstværk, og nogle var komplekse ting. Mine favoritter er dem, der involverer 3D-objekter, for eksempel dette hoppende bold og dette pakning terning.
I begyndelsen havde jeg ingen anelse om, hvordan disse blev lavet. Senere læste jeg og lærte meget af denne fine tutorial af Ana Tudor.
Som det viste sig, er det ikke meget anderledes at bygge og animere 3D-objekter med CSS fra at gøre det med Blender, bare med lidt anderledes smag.
Konklusion
I denne artikel undersøgte vi adfærden af CSS-animationer, når en animate-*
ejendom er ændret. Vi har især udarbejdet løsninger til "genafspilning af en animation" og "animation slow-mo".
Jeg håber, du finder denne artikel interessant. Fortæl mig venligst dine tanker!