Animerende højde - den rigtige måde

Lad os være ærlige. Animerende højde kan være en enorm smerte. For mig har det været en konstant kamp mellem at ville have pæne animationer og ikke være villig til at betale de enorme ydeevneomkostninger forbundet med animationshøjde. Nu - det er alt færdigt.

Det hele startede, da jeg arbejdede på mit sideprojekt - en genoptagelsesbygger, hvor du kan dele links til dit CV, der kun er aktive i en bestemt periode.

Jeg ville have en dejlig animation til alle sektionerne i CV'en, og jeg byggede en reaktionskomponent, der udførte animationen. Imidlertid opdagede jeg hurtigt, at denne komponent absolut ødelagde ydeevnen på lavere enheder og i nogle browsere. Helvede, selv min high-end Macbook pro kæmpede for at holde glatte fps på animationen.

Årsagen til dette er selvfølgelig, at animering af højdeegenskaben i CSS får browseren til at udføre dyre layout- og malingsoperationer på hver ramme. Der er et fantastisk afsnit om gengivelse af ydeevne på Google Web Fundamentals, jeg foreslår, at du tjekker det ud.

Kort fortalt - når du ændrer en geometrisk egenskab i css, skal browseren justere og udføre beregninger af, hvordan ændringen påvirker layoutet på siden, så bliver den nødt til at gengive siden igen i et trin kaldet maling.

Hvorfor skulle vi endda være interesserede i præstation?

Det kan være fristende at ignorere ydeevne. Det er ikke klogt, men det kan være fristende. Fra et forretningsmæssigt perspektiv sparer du meget tid, der ellers kan bruges på at opbygge nye funktioner.

Imidlertid kan ydeevne direkte påvirke din bundlinje. Hvad godt gør det for at opbygge mange funktioner, hvis ingen bruger dem? Flere undersøgelser udført af Amazon og Google viser, at dette er sandt. Ydeevne er direkte forbundet med applikationsbrug og bundlinjeindtægter.

Den anden side af ydeevne er lige så vigtig. Vi, som udviklere, har et ansvar for at sikre, at internettet forbliver tilgængeligt for alle - vi gør dette, fordi det er rigtigt. Fordi internettet ikke er til kun dig og mig, det er for alle.

Som det fremgår af Addy Osmanis fremragende artikel tager low-end-enheder betydeligt længere tid at parsere og udføre javascript sammenlignet med deres højere modstykker.

For at undgå at skabe en klasseskel på internettet er vi nødt til at være ubarmhjertige i vores stræben efter præstationer. For mig betød dette at være kreativ og finde et andet middel til at opnå mine animationer uden at ofre præstation.

Animer højden på den rigtige måde

Hvis du ikke er ligeglad med hvordan, og bare vil se et liveeksempel, se venligst nedenstående links for demo-side, eksempler og en npm-pakke til at reagere:

  • Demosite ved hjælp af teknikken
  • Levende eksempel i vanille JS
  • Simpel eksempel i reaktion
  • NPM-pakke og dokumentation til reaktion

Det spørgsmål, jeg stillede mig selv, var, hvordan jeg kunne undgå de præstationsomkostninger, der er forbundet med at animere højden. Simpelt svar - du kan ikke.

I stedet havde jeg brug for at blive kreativ med andre CSS-egenskaber, der ikke medfører disse omkostninger. Transformerer sig.

Da transformationer ikke har nogen måde at påvirke højden på. Vi kan ikke bare anvende en simpel egenskab på et element og gøres. Vi skal være mere kloge end det.

Den måde, vi vil bruge til at opnå performant animation af højden er faktisk ved at falske den med transform: scaleY. Animationen udføres i flere trin:

Her er et eksempel:

 ${this.markup} `
  • Først skal vi hente den oprindelige højde af elementbeholderen. Derefter indstiller vi den ydre beholderhøjde til at være eksplicit til denne højde. Dette får ethvert skiftende indhold til at løbe over containeren i stedet for at udvide dets overordnede.
  • Inde i den ydre beholder har vi en anden div, der er absolut positioneret til at spænde over divens bredde og højde. Dette er vores baggrund og skaleres, når vi skifter transformation.
  • Vi har også en indre container. Den indre beholder indeholder markeringen og ændrer højden i henhold til det indhold, den indeholder.
  • Her er tricket:  Når vi skifter en begivenhed, der ændrer markeringen, tager vi den nye højde af den indre beholder og beregner det beløb, som baggrunden skal skaleres for at imødekomme den nye højde. Derefter indstiller vi baggrunden til skalering Y med det nye beløb.
  • I vanille-javascript betyder det noget trickery med dobbelt gengivelser. Én gang for at få højden af ​​den indre beholder til at beregne skalaen. Derefter igen for at anvende skalaen på baggrundsdiv, så den udfører transformationen.

Du kan se et liveeksempel her i vanille JS.

På dette tidspunkt er vores baggrund blevet skaleret korrekt for at skabe illusionen om højden. Men hvad med det omgivende indhold? Da vi ikke længere justerer layout, påvirkes det omgivende indhold ikke af ændringerne.

At få det omgivende indhold til at bevæge sig. Vi er nødt til at justere indholdet ved hjælp af transformY. Tricket er at tage det beløb, som indholdet udvidede, og bruge dette til at flytte det omgivende indhold med transformationer. Se eksemplet ovenfor.

Animationshøjde i React

Som jeg tidligere nævnte, udviklede jeg denne metode, mens jeg arbejdede på et personligt projekt i React. Denne demo-side bruger denne metode udelukkende i al sin "højdeanimation". Tjek demosiden her.

Efter at have implementeret dette med succes tog jeg mig tid til at tilføje denne komponent og nogle understøttende komponenter til et lille animationsbibliotek, jeg har lavet i React. Hvis du er interesseret, kan du finde de relevante oplysninger her:

  • se biblioteket på NPM her
  • Dokumentation kan findes her.

De vigtigste komponenter i dette bibliotek er AnimateHeight og AnimateHeightContainer. Lad os undersøge dem:

// Inside a React component // handleAnimateHeight is called inside AnimateHeight and is passed the // transitionAmount and optionally selectedId if you pass that as a prop to // AnimateHeight. This means that you can use the transitionAmount to // transition your surrounding content.const handleAnimateHeight = (transitionAmount, selectedId) => { this.setState({ transitionAmount, selectedId }); }; // Takes a style prop, a shouldchange prop and a callback. shouldChange // determines when the AnimateHeight component should trigger, which is // whenever the prop changes. The same prop is used to control which // content to show.  {this.state.open && } {!this.state.open && } 
  • Eksempel med flytning af omgivende indhold

Ovenstående eksempler viser dig, hvordan du bruger AnimateHeight og manuelt udløser dit omgivende indhold til justering. Men hvad hvis du har meget indhold og ikke vil udføre denne proces manuelt? I så fald kan du bruge AnimateHeight i forbindelse med AnimateHeightContainer.

For at bruge AnimateHeightContainer skal du give alle børn på øverste niveau en prop kaldet animateHeightId, som også skal sendes til dine AnimateHeight-komponenter:

// Inside React Component const handleAnimateHeight = (transitionAmount, selectedId) => { this.setState({ transitionAmount, selectedId }); }; // AnimateHeight receives the transitionAmount and the active id of the AnimateHeight that fired. {this.state.open &&  {!this.state.open && }  // When AnimateHeight is triggered by state change // this content will move because the animateHeightId // is greater than the id of the AnimateHeight component above I will move I will also move 

Som du kan se fra dette eksempel modtager AnimateHeight de oplysninger, som det har brug for for at justere indholdet, når AnimateHeight-komponenten udløses ved at ændre tilstand.

Når dette sker, vil AnimateHeight-komponenten udløse tilbagekald og sætte egenskaberne i tilstand. Inde i AnimateHeight ser det sådan ud (forenklet):

// Inside AnimateHeight componentDidUpdate() { if (update) { doUpdate() callback(transitionAmount, this.props.animateHeightId) } } // Equivalent to calling this function: const handleAnimateHeight = (transitionAmount, selectedId) => { this.setState({ transitionAmount, selectedId }); }; handleAnimateHeight(transitionAmount, this.props.animateHeight)

Nu er du det beløb, som indholdet overgik i pixels, og id'et for AnimateHeight-komponenten, der affyrede. Når du først har videregivet disse værdier til AnimateHeightContainer, vil det tage dem og overføre de andre komponenter i sig selv, forudsat at du opretter stigende animateHeightIds på børn på øverste niveau.

Mere avancerede eksempler:

  • Flytning af omgivende indhold med AnimateHeightContainer
  • Harmonikaeksempel

BEMÆRK: Hvis du bruger denne metode til at animere højde og flytte omgivende indhold, skal du tilføje overgangsbeløbet til højden på din side.

Konklusion

Du har måske bemærket i denne artikel, at vi ikke faktisk animerer højde - og kalder det forkert. Du har selvfølgelig helt ret. Jeg er dog overbevist om, at det, vi kalder det, ikke betyder noget. Det der betyder noget er, at vi opnår den ønskede effekt med de lavest mulige omkostninger til ydelse.

Mens jeg tror, ​​jeg fandt en måde, der er bedre end at animere højdeegenskaben direkte, fremsætter jeg ingen krav om at have opfundet eller på anden måde tænkt på noget, der ikke er blevet opdaget før. Jeg dømmer heller ikke. Måske fungerer animationshøjde for dig i dit scenario - Intet problem.

Alt, hvad jeg ønsker, er at aktivere og forenkle effekter, som vi alle skal gøre, men nogle gange pådrage sig omkostninger, der er svære at bære. I det mindste vil jeg sætte gang i en diskussion, der er værd at have. Hvordan kan vi forbedre internettet for alle?