Hacking CSS Animation State og Playback Time PlatoBlockchain Data Intelligence. Vertikalt søk. Ai.

Hacking CSS-animasjonstilstand og avspillingstid

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:

[Innebygd innhold]

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"):

  1. De animation-name of <div> is none som standard.
  2. Brukeren klikker på en avmerkingsboks, animation-name blir spin, og animasjonen starter fra begynnelsen.
  3. Etter en stund klikker brukeren på den andre avmerkingsboksen. En ny CSS-regel trer i kraft, men animation-name is fortsatt spin, 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:

  1. animation-name is none i utgangspunktet.
  2. Brukeren klikker på "Spinn!", animation-name blir spin1. Animasjonen spin1 er startet fra begynnelsen fordi den nettopp ble lagt til.
  3. Brukeren klikker på "Spinn igjen!", animation-name blir spin2. Animasjonen spin2 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 is none 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".
  • spin1sin beslutning alltid overstyre spin2Det er fordi spin1 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, og spin2 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 har spin animasjon, men med forskjellige animation-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 av animation-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 ikke linear.
  • animation-direction er ikke normal.
  • flere verdier i animation-name med forskjellig animation-duration's og animation-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!

Tidstempel:

Mer fra CSS triks