Sa fim cinstiti. Înălțimea animată poate fi o durere imensă. Pentru mine, a fost o luptă constantă între dorința de a avea animații frumoase și nefiind dispus să plătesc costul enorm de performanță asociat cu înălțimea animației. Acum – gata.

Totul a început când lucram la proiectul meu secundar – un constructor de CV-uri, unde puteți partaja linkuri către CV-ul dvs., care sunt active doar pentru o anumită perioadă de timp.

Am vrut să am o animație drăguță pentru toate secțiunile din CV și am construit o componentă react care a realizat animația. Cu toate acestea, am descoperit curând că această componentă a distrus absolut performanța pe dispozitivele de nivel inferior și în unele browsere. La naiba, chiar și Macbook-ul meu de ultimă generație se străduia să păstreze imagini fps pe animație.

Motivul pentru aceasta este, desigur, că animarea proprietății înălțimii din CSS determină browserul să efectueze operațiuni costisitoare de layout și vopsire pe fiecare cadru. Există o secțiune fantastică despre randarea performanței la Google Web Fundamentals, vă sugerez să o verificați.

Pe scurt, totuși – ori de câte ori schimbați o proprietate geometrică în CSS, browserul trebuie să regleze și să efectueze calcule cu privire la modul în care această schimbare afectează aspectul paginii, atunci va trebui să redea pagina într-un pas numit vopsea.

De ce ar trebui să ne pese chiar de performanță?

Poate fi tentant să ignori performanța. Nu este înțelept, dar poate fi tentant. Din perspectiva afacerii, economisiți mult timp care altfel poate fi cheltuit construind noi funcții.

Cu toate acestea, performanța vă poate influența în mod direct rezultatele. La ce bun este să construiești o mulțime de caracteristici, dacă nimeni nu le folosește? Studiile multiple efectuate de Amazon și Google arată că acest lucru este adevărat. Performanța este direct legată de utilizarea aplicației și de veniturile din linia de jos.

Cealaltă parte a performanței este la fel de importantă. Noi, ca dezvoltatori, avem responsabilitatea de a ne asigura că internetul rămâne accesibil tuturor – facem acest lucru pentru că este corect. Pentru că internetul nu este destinat doar pentru tine și pentru mine, este pentru toată lumea.

După cum se dovedește din cel al lui Addy Osmani articol excelent, dispozitivele low-end durează mult mai mult pentru a analiza și executa javascript în comparație cu omologii lor superiori.

Pentru a evita crearea unei diviziuni de clasă pe internet, trebuie să fim neobosiți în căutarea performanței. Pentru mine asta însemna să fiu creativ și să găsesc un alt mijloc de a-mi realiza animațiile fără a sacrifica performanța.

Animează înălțimea în modul corect

Dacă nu vă pasă cum și doriți doar să vedeți un exemplu live, consultați linkurile de mai jos pentru site-ul demonstrativ, exemple și un pachet npm pentru reacție:

Întrebarea pe care mi-am pus-o era cum aș putea evita costul de performanță pe care îl suportă înălțimea animării. Răspuns simplu – nu poți.

În schimb, trebuia să devin creativ cu alții Proprietăți CSS care nu implică aceste costuri. Anume se transformă.

Întrucât transformările nu au nicio modalitate de a influența înălțimea. Nu putem aplica pur și simplu o proprietate simplă unui element și să se termine. Trebuie să fim mai deștepți decât atât.

Modul în care vom folosi pentru a realiza o animație performantă a înălțimii este de fapt falsificându-l cu transform: scaleY. Animația se face în mai mulți pași:

Iată un exemplu:

<div class=”ah-outercontainer”>  
<div class=”ah-background” style=”${this.getBackgroundStyle()}”></div>
<div class=”ah-innercontainer”>${this.markup}</div></div>`
  • Mai întâi trebuie să obținem înălțimea inițială a containerului elementului. Apoi, setăm înălțimea exterioară a containerului pentru a fi explicită la această înălțime. Acest lucru va face ca orice conținut în schimbare să depășească containerul, în loc să-și extindă părintele.
  • În interiorul containerului exterior avem un alt div care este poziționat absolut pentru a acoperi întreaga lățime și înălțime a divului. Acesta este fundalul nostru și va fi scalat odată ce comutăm transformarea.
  • Avem și un container interior. Containerul interior conține marcajul și își va schimba înălțimea în funcție de conținutul pe care îl conține.
  • Iată trucul: Odată ce comutăm un eveniment care schimbă marcajul, luăm noua înălțime a containerului interior și calculăm cantitatea de care trebuie să se întindă fundalul pentru a se potrivi noii înălțimi. Apoi am setat fundalul la scaleY cu noua sumă.
  • În javascript de vanilie, acest lucru înseamnă unele trucuri cu randări duale. O dată pentru a obține înălțimea containerului interior pentru a calcula scala. Apoi, din nou, pentru a aplica scala pe fundalul div, astfel încât să efectueze transformarea.

Puteți vedea un exemplu live aici în vanilie JS.

În acest moment, fundalul nostru a fost scalat corespunzător pentru a crea iluzia înălțimii. Dar ce zici de conținutul din jur? Deoarece nu mai ajustăm aspectul, conținutul din jur nu este afectat de modificări.

Pentru a face mișcarea conținutului din jur. Trebuie să ajustăm conținutul folosind transformY. Trucul este să luați cantitatea extinsă de conținut și să o utilizați pentru a muta conținutul înconjurător cu transformări. Vezi exemplul de mai sus.

Înălțime animată în React

După cum am menționat anterior, am dezvoltat această metodă în timp ce lucram la un proiect personal în React. Acest site demonstrativ folosește această metodă exclusiv în toate „animațiile pe înălțime”. Consultați site-ul demo aici.

După ce am implementat acest lucru cu succes, mi-am luat timp să adaug această componentă și câteva componente de sprijin într-o mică bibliotecă de animație pe care am creat-o în React. Dacă sunteți interesat, puteți găsi informațiile relevante aici:

Cele mai importante componente din această bibliotecă sunt AnimateHeight și AnimateHeightContainer. Să le examinăm:

// 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.

<AnimateHeight 
  style={{ backgroundColor: "#f2f2f2" }} 
  shouldChange={this.state.open}   
  callback={this.handleAnimateHeight}
>
  {this.state.open && <Component />}
  {!this.state.open && <AnotherComponent />}
</AnimateHeight>

Exemplele de mai sus vă arată cum să utilizați AnimateHeight și să declanșați manual conținutul din jur pentru a le ajusta. Dar dacă aveți mult conținut și nu doriți să faceți acest proces manual? În acest caz, puteți utiliza AnimateHeight împreună cu AnimateHeightContainer.

Pentru a utiliza AnimateHeightContainer, trebuie să le oferiți tuturor copiilor de nivel superior un accesoriu numit animateHeightId, care trebuie, de asemenea, să fie transmis componentelor dvs. AnimateHeight:

// Inside React Component
const handleAnimateHeight = (transitionAmount, selectedId) => {     
this.setState({ transitionAmount, selectedId });
};

// AnimateHeight receives the transitionAmount and the active id of the AnimateHeight that fired. 
<AnimateHeightContainer
  transitionAmount={this.state.transitionAmount}
  selectedId={this.state.selectedId}
>
  <div animateHeightId={1}}>
    <AnimateHeight 
      style={{ backgroundColor: "#f2f2f2" }}
      shouldChange={this.state.open}
      callback={this.handleAnimateHeight}
      animateHeightId={1}
    > 
    {this.state.open && <Component />
    {!this.state.open && <AnotherComponent />}  
    <AnimateHeight />
          
    // When AnimateHeight is triggered by state change
    // this content will move because the animateHeightId
    // is greater than the id of the AnimateHeight component above
    <div animateHeightId={2}>I will move</div>
    <div animateHeightId={3}>I will also move</div>
<AnimateHeightContainer />

După cum puteți vedea din acest exemplu, AnimateHeight primește informațiile de care are nevoie pentru a regla conținutul atunci când componenta AnimateHeight este declanșată prin schimbarea stării.

Odată ce acest lucru se întâmplă, componenta AnimateHeight va declanșa apelul invers și va seta proprietățile în stare. În interiorul AnimateHeight arată cam așa (simplificat):

// 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)

Acum, veți trece conținutul în pixeli și id-ul componentei AnimateHeight care a fost declanșată. Odată ce treceți aceste valori către AnimateHeightContainer, acestea le vor lua și vor face tranziția celorlalte componente din interiorul său, cu condiția să configurați incrementarea animateHeightIds pe copiii de nivel superior.

Exemple mai avansate:

NOTĂ: Dacă utilizați această metodă pentru a anima înălțimea și a muta conținutul înconjurător, trebuie să adăugați suma de tranziție la înălțimea paginii dvs.

Concluzie

Este posibil să fi observat în acest articol că de fapt nu animăm înălțimea – și că numim asta este greșit. Ai desigur că ai absolut dreptate. Cu toate acestea, cred cu tărie că ceea ce noi numim nu contează. Ceea ce contează este că obținem efectul dorit cu cel mai mic cost posibil pentru performanță.

Deși cred că am găsit o modalitate mai bună decât animarea directă a proprietății înălțimii, nu susțin că am inventat sau gândit altfel ceva care nu a fost descoperit până acum. Nici eu nu judec. Poate că înălțimea de animație funcționează pentru dvs. în scenariul dvs. – Nicio problemă.

Tot ce vreau este să permit și să simplific efectele pe care trebuie să le facem cu toții, dar uneori suportă costuri greu de suportat. Cel puțin, vreau să declanșez o discuție care merită purtată. Cum putem îmbunătăți internetul pentru toată lumea?