Wolfenstein endast för CSS är ett litet projekt som jag gjorde för några veckor sedan. Det var ett experiment med CSS 3D-transformationer och animationer.
Inspirerad av den FPS demo och en annan Wolfenstein CodePen, jag bestämde mig för att bygga min egen version. Det är löst baserat på avsnitt 1 – våning 9 av det ursprungliga Wolfenstein 3D-spelet.
Redaktör: Det här spelet kräver avsiktligt lite snabb reaktion för att undvika en Game Over-skärm.
Här är en genomspelningsvideo:
I ett nötskal är mitt projekt inget annat än en noggrant skriptad lång CSS-animation. Plus några exempel på hack i kryssrutan.
:checked ~ div { animation-name: spin; }
Miljön består av 3D-rutnätsytor och animationerna är mestadels vanliga 3D-översättningar och -rotationer. Inget riktigt fancy.
Två problem var dock särskilt svåra att lösa:
- Spela animationen "vapenskjutning" när spelaren klickar på en fiende.
- När den snabbrörliga chefen fick den sista träffen, gå in i en dramatisk slow motion.
På teknisk nivå innebar detta:
- Spela upp en animation igen när nästa kryssruta är markerad.
- Sakta ner en animering när en kryssruta är markerad.
I själva verket var inget av dem ordentligt löst i mitt projekt! Det slutade med att jag använde lösningar eller så gav jag bara upp.
Å andra sidan, efter lite grävande, hittade jag till slut nyckeln till båda problemen: att ändra egenskaperna för att köra CSS-animationer. I den här artikeln kommer vi att utforska mer om detta ämne:
- Många interaktiva exempel.
- Dissektioner: hur fungerar varje exempel (eller fungerar inte)?
- Bakom scenen: hur hanterar webbläsare animerade tillstånd?
Låt mig "kasta mina tegelstenar".
Problem 1: Spela om animation
Det första exemplet: "bara en annan kryssruta"
Min första intuition var "lägg bara till en annan kryssruta", vilket inte fungerar:
Varje kryssruta fungerar individuellt, men inte båda tillsammans. Om en kryssruta redan är markerad fungerar inte den andra längre.
Så här fungerar det (eller "fungerar inte"):
- Smakämnen
animation-name
of<div>
isnone
som standard. - Användaren klickar på en kryssruta,
animation-name
blirspin
, och animeringen börjar från början. - Efter ett tag klickar användaren på den andra kryssrutan. En ny CSS-regel träder i kraft, men
animation-name
is fortfarandespin
, vilket innebär att ingen animering läggs till eller tas bort. Animationen fortsätter helt enkelt att spelas som om ingenting hade hänt.
Det andra exemplet: "klona animationen"
Ett arbetssätt är att klona animationen:
#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2; }
Såhär fungerar det:
animation-name
isnone
initialt.- Användaren klickar på "Snurra!",
animation-name
blirspin1
. Animationenspin1
startas från början eftersom den precis lades till. - Användaren klickar på "Snurra igen!",
animation-name
blirspin2
. Animationenspin2
startas från början eftersom den precis lades till.
Observera att i steg #3, spin1
tas bort på grund av ordningen i CSS-reglerna. Det kommer inte att fungera om "Snurra igen!" kontrolleras först.
Det tredje exemplet: "lägga till samma animation"
Ett annat arbetssätt är att "lägga till samma animation":
#spin1:checked ~ div { animation-name: spin; }
#spin2:checked ~ div { animation-name: spin, spin; }
Detta liknar det föregående exemplet. Du kan faktiskt förstå beteendet så här:
#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2, spin1; }
Observera att när "Snurra igen!" är markerad, blir den gamla körande animationen den andra animeringen i den nya listan, vilket kan vara ointuitivt. En direkt konsekvens är: tricket fungerar inte om animation-fill-mode
is forwards
. Här är en demo:
Om du undrar varför detta är fallet, här är några ledtrådar:
animation-fill-mode
isnone
som standard, vilket betyder "Animeringen har ingen effekt alls om den inte spelas upp".animation-fill-mode: forwards;
betyder "När animationen har spelat klart måste den stanna vid den sista nyckelbildrutan för alltid".spin1
s beslut alltid åsidosättaspin2
beror på detspin1
visas senare i listan.- Anta att användaren klickar på "Snurra!", väntar på ett helt snurr och sedan klickar på "Snurra igen!". I detta ögonblick.
spin1
är redan färdig, ochspin2
bara börjar.
Diskussion
Tumregel: du kan inte "starta om" en befintlig CSS-animation. Istället vill du lägga till och spela upp en ny animation. Detta kan bekräftas av W3C-specifikationen:
När en animation har startat fortsätter den tills den slutar eller animationsnamnet tas bort.
När jag nu jämför de två sista exemplen tror jag i praktiken att "kloning av animationer" ofta borde fungera bättre, speciellt när CSS-förprocessor är tillgänglig.
Problem 2: Slow Motion
Man kan tro att sakta ner en animation bara är en fråga om att ställa in en längre animation-duration
:
div { animation-duration: 0.5s; }
#slowmo:checked ~ div { animation-duration: 1.5s; }
Det här fungerar faktiskt:
... eller gör det?
Med några justeringar borde det vara lättare att se problemet.
Ja, animeringen saktas ner. Och nej, det ser inte bra ut. Hunden "hoppar" (nästan) alltid när du växlar kryssrutan. Dessutom verkar hunden hoppa till en slumpmässig position snarare än den ursprungliga. Hurså?
Det skulle vara lättare att förstå det om vi introducerade två "skuggelement":
Båda skuggelementen kör samma animationer med olika animation-duration
. Och de påverkas inte av kryssrutan.
När du växlar kryssrutan växlar elementet omedelbart mellan tillstånden för två skuggelement.
citerar W3C-specifikationen:
Ändringar av värdena för animeringsegenskaper medan animeringen körs gäller som om animeringen hade dessa värden från när den började.
Detta följer statslösa design, vilket gör det möjligt för webbläsare att enkelt bestämma det animerade värdet. Den faktiska beräkningen beskrivs här. och här..
Ännu ett försök
En idé är att pausa den aktuella animeringen och sedan lägga till en långsammare animering som tar över därifrån:
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 fungerar:
... eller gör det?
Det saktar ner när du klickar på "Slowmo!". Men om du väntar på en hel cirkel kommer du att se ett "hopp". Egentligen hoppar den alltid till positionen när "Slowmo!" klickas på.
Anledningen är att vi inte har en from
keyframe definierad – och det borde vi inte. När användaren klickar på "Slowmo!", spin1
är pausad vid någon position, och spin2
börjar i exakt samma position. Vi kan helt enkelt inte förutse den positionen i förväg ... eller kan vi?
En fungerande lösning
Vi kan! Genom att använda en anpassad egenskap kan vi fånga vinkeln i den första animeringen och sedan skicka den till den andra animeringen:
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);
}
}
Notera: @property
används i detta exempel, dvs stöds inte av alla webbläsare.
Den "perfekta" lösningen
Det finns en varning till den tidigare lösningen: "avsluta slowmo" fungerar inte bra.
Här är en bättre lösning:
I den här versionen kan slow motion aktiveras eller avslutas sömlöst. Ingen experimentell funktion används heller. Så är det den perfekta lösningen? Ja och nej.
Den här lösningen fungerar som att "växla" "växlar":
- Kugghjul: det finns två
<div>
s. Den ena är den andras förälder. Båda harspin
animation men med olikaanimation-duration
. Det slutliga tillståndet för elementet är ackumuleringen av båda animationerna. - Skiftning: I början bara en
<div>
har sin animation igång. Den andra är pausad. När kryssrutan är aktiverad byter båda animationerna status.
Även om jag verkligen gillar resultatet, finns det ett problem: det är ett trevligt utnyttjande av spin
animation, som inte fungerar för andra typer av animationer i allmänhet.
En praktisk lösning (med JS)
För allmänna animationer är det möjligt att uppnå slow motion-funktionen med lite JavaScript:
En snabb förklaring:
- En anpassad egenskap används för att spåra animeringens framsteg.
- Animeringen "startas om" när kryssrutan är växlad.
- JS-koden beräknar rätt
animation-delay
för att säkerställa en sömlös övergång. jag rekomenderar den här artikeln om du inte är bekant med negativa värden påanimation-delay
.
Du kan se den här lösningen som en hybrid av "att starta om animation" och "växling"-metoden.
Här är det viktigt att spåra animeringens framsteg korrekt. Lösningar är möjliga om @property
är inte tillgänglig. Som ett exempel använder denna version z-index
för att spåra framstegen:
Sidanteckning: ursprungligen försökte jag också skapa en CSS-version men det lyckades inte. Även om jag inte är 100% säker, tror jag att det beror på animation-delay
is inte animerbar.
Här är en version med minimal JavaScript. Endast "att gå in i slowmo" fungerar.
Meddela mig om du lyckas skapa en fungerande CSS-version!
Slow-mo valfri animering (med JS)
Slutligen skulle jag vilja dela en lösning som fungerar för (nästan) alla animationer, även med flera komplicerade @keyframes
:
I grund och botten måste du lägga till en animationsförloppsspårare och sedan noggrant beräkna animation-delay
för den nya animationen. Men ibland kan det vara knepigt (men möjligt) att få rätt värden.
Till exempel:
animation-timing-function
är intelinear
.animation-direction
är intenormal
.- flera värden i
animation-name
med olikaanimation-duration
s ochanimation-delay
S.
Denna metod beskrivs också här. för Web Animations API.
Erkännanden
Jag började på den här vägen efter att ha stött på CSS-bara projekt. Någon stans delikat konstverk, och några var komplexa tillbehör. Mina favoriter är de som involverar 3D-objekt, till exempel detta studsande boll och detta förpackningskub.
I början hade jag ingen aning om hur dessa gjordes. Senare läste jag och lärde mig mycket av denna trevliga handledning av Ana Tudor.
Det visade sig att bygga och animera 3D-objekt med CSS är inte mycket annorlunda än att göra det med Blandare, bara med lite annorlunda smak.
Slutsats
I den här artikeln undersökte vi beteendet hos CSS-animationer när en animate-*
egendomen ändras. Speciellt utarbetade vi lösningar för att "spela om en animation" och "animation slow-mo".
Jag hoppas att du tycker att den här artikeln är intressant. Snälla låt mig få veta vad du tänker!