Wolfenstein kun for CSS er et lite prosjekt som jeg laget for noen uker siden. Det var et eksperiment med CSS 3D-transformasjoner og animasjoner.
Inspirert av FPS demo og en annen Wolfenstein CodePen, bestemte jeg meg for å bygge min egen versjon. Det er løst basert på episode 1 – etasje 9 av det originale Wolfenstein 3D-spillet.
Redaktør: Dette spillet krever med vilje litt rask reaksjon for å unngå en Game Over-skjerm.
Her er en gjennomspillingsvideo:
I et nøtteskall er prosjektet mitt ikke annet enn en nøye skriptet lang CSS-animasjon. Pluss noen få tilfeller av avkrysningsboks hack.
:checked ~ div { animation-name: spin; }
Miljøet består av 3D-rutenettflater, og animasjonene er for det meste vanlige 3D-oversettelser og -rotasjoner. Ikke noe fancy.
Imidlertid var to problemer spesielt vanskelige å løse:
- Spill "våpenskyting"-animasjonen hver gang spilleren klikker på en fiende.
- Når den raske sjefen fikk det siste treffet, gå inn i en dramatisk sakte film.
På teknisk nivå betydde dette:
- Spill av en animasjon på nytt når neste avmerkingsboks er merket av.
- Senk en animasjon når en avkrysningsboks er merket av.
Faktisk ble ingen av dem riktig løst i prosjektet mitt! Jeg endte enten opp med å bruke løsninger eller bare ga opp.
På den annen side, etter litt graving, fant jeg til slutt nøkkelen til begge problemene: å endre egenskapene til å kjøre CSS-animasjoner. I denne artikkelen vil vi utforske videre om dette emnet:
- Mange interaktive eksempler.
- Disseksjoner: hvordan fungerer hvert eksempel (eller fungerer ikke)?
- Bak kulissen: hvordan håndterer nettlesere animasjonstilstander?
La meg "kaste klossene mine".
Problem 1: Spill av animasjon på nytt
Det første eksemplet: "bare en annen avkrysningsboks"
Min første intuisjon var "bare legg til en avkrysningsboks", som ikke fungerer:
Hver avmerkingsboks fungerer individuelt, men ikke begge sammen. Hvis en avkrysningsboks allerede er merket, fungerer ikke den andre lenger.
Slik fungerer det (eller "fungerer ikke"):
- De
animation-name
of<div>
isnone
som standard. - Brukeren klikker på en avmerkingsboks,
animation-name
blirspin
, og animasjonen starter fra begynnelsen. - Etter en stund klikker brukeren på den andre avmerkingsboksen. En ny CSS-regel trer i kraft, men
animation-name
is fortsattspin
, som betyr at ingen animasjon legges til eller fjernes. Animasjonen fortsetter ganske enkelt som om ingenting har skjedd.
Det andre eksemplet: "kloning av animasjonen"
En arbeidstilnærming er å klone animasjonen:
#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2; }
Her er hvordan det fungerer:
animation-name
isnone
i utgangspunktet.- Brukeren klikker på "Spinn!",
animation-name
blirspin1
. Animasjonenspin1
er startet fra begynnelsen fordi den nettopp ble lagt til. - Brukeren klikker på "Spinn igjen!",
animation-name
blirspin2
. Animasjonenspin2
er startet fra begynnelsen fordi den nettopp ble lagt til.
Merk at i trinn #3, spin1
er fjernet på grunn av rekkefølgen til CSS-reglene. Det vil ikke fungere hvis "Spinn igjen!" sjekkes først.
Det tredje eksemplet: «legge til den samme animasjonen»
En annen arbeidstilnærming er å "legge til den samme animasjonen":
#spin1:checked ~ div { animation-name: spin; }
#spin2:checked ~ div { animation-name: spin, spin; }
Dette ligner på forrige eksempel. Du kan faktisk forstå oppførselen på denne måten:
#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2, spin1; }
Merk at når "Spinn igjen!" er merket av, blir den gamle kjørende animasjonen den andre animasjonen i den nye listen, noe som kan være lite intuitivt. En direkte konsekvens er: trikset vil ikke fungere hvis animation-fill-mode
is forwards
. Her er en demo:
Hvis du lurer på hvorfor dette er tilfelle, her er noen ledetråder:
animation-fill-mode
isnone
som standard, som betyr "Animasjonen har ingen effekt i det hele tatt hvis den ikke spilles".animation-fill-mode: forwards;
betyr "Etter at animasjonen er ferdig avspilt, må den forbli i siste nøkkelbilde for alltid".spin1
sin beslutning alltid overstyrespin2
Det er fordispin1
vises senere i listen.- Anta at brukeren klikker på "Spinn!", venter på et helt spinn, og deretter klikker på "Spinn igjen!". Akkurat nå.
spin1
er allerede ferdig, ogspin2
bare starter.
Diskusjon
Tommelfingerregel: du kan ikke "starte på nytt" en eksisterende CSS-animasjon. I stedet vil du legge til og spille av en ny animasjon. Dette kan bekreftes av W3C-spesifikasjonen:
Når en animasjon har startet, fortsetter den til den slutter eller animasjonsnavnet fjernes.
Når jeg nå sammenligner de to siste eksemplene, tror jeg i praksis at "kloning av animasjoner" ofte bør fungere bedre, spesielt når CSS-forprosessor er tilgjengelig.
Oppgave 2: Slow Motion
Man kan tro at det å bremse en animasjon bare er et spørsmål om å sette en lengre animation-duration
:
div { animation-duration: 0.5s; }
#slowmo:checked ~ div { animation-duration: 1.5s; }
Dette fungerer faktisk:
… eller gjør det?
Med noen få justeringer bør det være lettere å se problemet.
Ja, animasjonen er bremset. Og nei, det ser ikke bra ut. Hunden "hopper" (nesten) alltid når du veksler på avmerkingsboksen. Videre ser det ut til at hunden hopper til en tilfeldig posisjon i stedet for den opprinnelige. Hvorfor det?
Det ville være lettere å forstå det hvis vi introduserte to "skyggeelementer":
Begge skyggeelementene kjører de samme animasjonene med forskjellige animation-duration
. Og de påvirkes ikke av avmerkingsboksen.
Når du bytter avmerkingsboksen, bytter elementet umiddelbart mellom tilstandene til to skyggeelementer.
siterer W3C-spesifikasjonen:
Endringer i verdiene til animasjonsegenskaper mens animasjonen kjører, gjelder som om animasjonen hadde disse verdiene fra da den startet.
Dette følger statsløs design, som lar nettlesere enkelt bestemme den animerte verdien. Selve beregningen er beskrevet her. og her..
Et nytt forsøk
En idé er å sette den gjeldende animasjonen på pause, og deretter legge til en langsommere animasjon som tar 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 fungerer:
… eller gjør det?
Det bremser ned når du klikker på "Slowmo!". Men hvis du venter på en hel sirkel, vil du se et "hopp". Faktisk hopper den alltid til posisjonen når "Slowmo!" er klikket på.
Grunnen er at vi ikke har en from
keyframe definert – og det burde vi ikke. Når brukeren klikker på "Slowmo!", spin1
er stoppet i en eller annen posisjon, og spin2
starter i nøyaktig samme posisjon. Vi kan rett og slett ikke forutsi den posisjonen på forhånd ... eller kan vi det?
En fungerende løsning
Vi kan! Ved å bruke en egendefinert egenskap kan vi fange vinkelen i den første animasjonen, og deretter overføre den til den andre animasjonen:
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);
}
}
OBS: @property
brukes i dette eksemplet, som er støttes ikke av alle nettlesere.
Den "perfekte" løsningen
Det er et forbehold til den forrige løsningen: "avslutte slowmo" fungerer ikke bra.
Her er en bedre løsning:
I denne versjonen kan sakte film gå inn eller ut sømløst. Ingen eksperimentell funksjon brukes heller. Så er det den perfekte løsningen? Ja og nei.
Denne løsningen fungerer som å "skifte" "gir":
- Gir: det er to
<div>
s. Den ene er forelder til den andre. Begge harspin
animasjon, men med forskjelligeanimation-duration
. Den endelige tilstanden til elementet er akkumuleringen av begge animasjonene. - Skifting: I begynnelsen, bare én
<div>
har sin animasjon i gang. Den andre er satt på pause. Når avmerkingsboksen er vekslet, bytter begge animasjonene status.
Selv om jeg virkelig liker resultatet, er det ett problem: det er en fin utnyttelse av spin
animasjon, som ikke fungerer for andre typer animasjoner generelt.
En praktisk løsning (med JS)
For generelle animasjoner er det mulig å oppnå sakte filmfunksjonen med litt JavaScript:
En rask forklaring:
- En egendefinert egenskap brukes til å spore animasjonsfremdriften.
- Animasjonen "startes på nytt" når avmerkingsboksen er vekslet.
- JS-koden beregner riktig
animation-delay
for å sikre en sømløs overgang. jeg anbefaler denne artikkelen hvis du ikke er kjent med negative verdier avanimation-delay
.
Du kan se på denne løsningen som en hybrid av "restarting animation" og "gearshifting"-tilnærmingen.
Her er det viktig å spore animasjonsfremdriften riktig. Løsninger er mulige hvis @property
er ikke tilgjengelig. Som et eksempel bruker denne versjonen z-index
for å spore fremdriften:
Sidenotat: opprinnelig prøvde jeg også å lage en CSS-versjon, men lyktes ikke. Selv om jeg ikke er 100% sikker, tror jeg det er fordi animation-delay
is ikke animerbar.
Her er en versjon med minimal JavaScript. Bare «å gå inn i slowmo» fungerer.
Gi meg beskjed hvis du klarer å lage en fungerende CSS-versjon!
Slow-mo hvilken som helst animasjon (med JS)
Til slutt vil jeg dele en løsning som fungerer for (nesten) alle animasjoner, selv med flere kompliserte @keyframes
:
I utgangspunktet må du legge til en animasjonsfremdriftssporer, og deretter beregne nøye animation-delay
for den nye animasjonen. Noen ganger kan det imidlertid være vanskelig (men mulig) å få de riktige verdiene.
For eksempel:
animation-timing-function
er ikkelinear
.animation-direction
er ikkenormal
.- flere verdier i
animation-name
med forskjelliganimation-duration
's oganimation-delay
'S.
Denne metoden er også beskrevet her. for Web Animations API.
Erkjennelsene
Jeg begynte på denne banen etter å ha møtt CSS-bare-prosjekter. Noen var delikat kunstverk, og noen var det komplekse innretninger. Mine favoritter er de som involverer 3D-objekter, for eksempel dette sprettball og denne pakking kube.
I begynnelsen hadde jeg ingen anelse om hvordan disse ble laget. Senere leste jeg og lærte mye av denne fine opplæringen av Ana Tudor.
Det viste seg, å bygge og animere 3D-objekter med CSS er ikke mye forskjellig fra å gjøre det med Blender, bare med litt annen smak.
konklusjonen
I denne artikkelen undersøkte vi oppførselen til CSS-animasjoner når en animate-*
eiendommen er endret. Spesielt har vi utarbeidet løsninger for "replaying an animation" og "animation slow-mo".
Jeg håper du finner denne artikkelen interessant. Vennligst gi meg beskjed om dine tanker!