Животната височина - правилният начин

Нека бъдем честни. Животната височина може да бъде огромна болка. За мен това е постоянна битка между желанието да имаме хубави анимации и нежеланието да платя огромните разходи за производителност, свързани с анимацията на височината. Сега - това е всичко.

Всичко започна, когато работех върху моя страничен проект - конструктор на автобиографии, където можете да споделяте връзки към автобиографията си, които са активни само за определен период от време.

Исках да имам хубава анимация за всички раздели в автобиографията и изградих реакционен компонент, който изпълнява анимацията. Скоро обаче открих, че този компонент абсолютно унищожава производителността на устройства от по-нисък клас и в някои браузъри. По дяволите, дори моят Macbook pro от висок клас се мъчеше да поддържа плавни fps в анимацията.

Причината за това е, разбира се, че анимирането на свойството височина в CSS кара браузъра да извършва скъпи операции по оформление и рисуване на всеки кадър. Има фантастичен раздел за рендиране на ефективността в Google Web Fundamentals, предлагам ви да го разгледате.

Накратко - винаги, когато променяте геометрично свойство в css, браузърът трябва да коригира и извършва изчисления за това как тази промяна влияе върху оформлението на страницата, тогава ще трябва да изобрази отново страницата в стъпка, наречена боя.

Защо изобщо да ни е грижа за представянето?

Може да бъде изкушаващо да се игнорира производителността. Това не е разумно, но може да бъде изкушаващо. От гледна точка на бизнеса спестявате много време, което иначе може да бъде изразходвано за изграждане на нови функции.

Ефективността обаче може пряко да повлияе на крайния резултат. Каква полза от изграждането на много функции, ако никой не ги използва? Множество проучвания, направени от Amazon и Google, показват, че това е вярно. Ефективността е пряко свързана с използването на приложения и приходите от крайния ред.

Другата страна на представянето е също толкова важна. Ние като разработчици носим отговорността да гарантираме, че мрежата ще остане достъпна за всички - правим това, защото е правилно. Тъй като интернет не е само за вас и мен, той е за всички.

Както се вижда от отличната статия на Addy Osmani, устройствата от нисък клас отнемат значително повече време за синтактичен анализ и изпълнение на javascript в сравнение с колегите от по-висок клас.

За да избегнем създаването на класово разделение в интернет, трябва да бъдем неумолими в стремежа си към изпълнение. За мен това означаваше да бъда креативен и да намеря друго средство за постигане на анимациите си, без да жертвам изпълнението.

Анимирайте височината по правилния начин

Ако не ви интересува как и просто искате да видите пример на живо, моля, вижте връзките по-долу за демонстрационен сайт, примери и npm пакет за реакция:

  • Демо сайт, използващ техниката
  • Пример на живо във ванилия JS
  • Прост пример за реакция
  • NPM пакет и документация за реакция

Въпросът, който си зададох, беше как бих могъл да избегна разходите за изпълнение, които възникват чрез анимиране на височината. Прост отговор - не можете.

Вместо това трябваше да проявя креативност с други CSS свойства, които не носят тези разходи. А именно трансформира.

Тъй като трансформациите нямат начин да влияят на височината. Не можем просто да приложим просто свойство към елемент и да бъде готово. Трябва да сме по-умни от това.

Начинът, по който ще използваме за постигане на ефективна анимация на височината, всъщност е като я фалшифицираме с transform: scaleY. Анимацията се извършва в няколко стъпки:

Ето пример:

 ${this.markup} `
  • Първо трябва да получим първоначалната височина на контейнера с елементи. След това задаваме височината на външния контейнер да бъде явна на тази височина. Това ще накара всяко променящо се съдържание да препълни контейнера, вместо да разшири родителя си.
  • Във външния контейнер имаме още div, който е абсолютно позициониран да обхваща цялата ширина и височина на div. Това е нашият фон и ще бъде мащабиран, след като превключим трансформацията.
  • Разполагаме и с вътрешен контейнер. Вътрешният контейнер съдържа маркировката и ще промени височината си според съдържанието, което съдържа.
  • Ето трикът:  След като превключим събитие, което променя маркировката, вземаме новата височина на вътрешния контейнер и изчисляваме количеството, което фонът трябва да мащабира, за да побере новата височина. След това задаваме фона на мащаба Y по новата сума.
  • Във ванилов javascript това означава известна хитрост с двойни рендери. Веднъж, за да получите височината на вътрешния контейнер, за да изчислите скалата. След това отново да приложите скалата към фоновия div, така че да извърши трансформацията.

Можете да видите пример на живо тук във ванилия JS.

В този момент нашият фон е правилно мащабиран, за да създаде илюзия за височина. Но какво ще кажете за околното съдържание? Тъй като вече не коригираме оформлението, околното съдържание не е засегнато от промените.

За да накарате околното съдържание да се движи. Трябва да коригираме съдържанието с помощта на transformY. Номерът е да вземете количеството, което съдържанието се е разширило, и да го използвате, за да преместите околното съдържание с преобразувания. Вижте примера по-горе.

Анимационна височина в React

Както споменах по-рано, разработих този метод, докато работех по личен проект в React. Този демонстрационен сайт използва този метод изключително във всичките си „анимации на височина“. Вижте демонстрационния сайт тук.

След като реализирах това с успех, отделих време да добавя този компонент и някои поддържащи компоненти към малка анимационна библиотека, която направих в React. Ако се интересувате, можете да намерите съответната информация тук:

  • вижте библиотеката на NPM тук
  • Документация можете да намерите тук.

Най-важните компоненти в тази библиотека е AnimateHeight и AnimateHeightContainer. Нека ги разгледаме:

// 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 && } 
  • Пример за движещо се околно съдържание

Горните примери ви показват как да използвате AnimateHeight и ръчно да задействате околното съдържание, за да го настроите. Но какво, ако имате много съдържание и не искате да правите този процес ръчно? В този случай можете да използвате AnimateHeight заедно с AnimateHeightContainer.

За да използвате AnimateHeightContainer, трябва да предоставите на всички деца от най-високо ниво подпора, наречена animateHeightId, която също трябва да бъде предадена на вашите компоненти 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. {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 

Както можете да видите от този пример AnimateHeight получава информацията, която му е необходима, за да коригира съдържанието, когато компонентът AnimateHeight се задейства чрез промяна на състоянието.

След като това се случи, компонентът AnimateHeight ще задейства обратно извикване и ще зададе свойствата в състояние. Вътре в AnimateHeight изглежда нещо подобно (опростено):

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

Сега вие определяте количеството, което съдържанието е преминало в пиксели, и идентификатора на изстреляния компонент AnimateHeight. След като предадете тези стойности на AnimateHeightContainer, той ще ги вземе и ще прехвърли останалите компоненти в себе си, при условие че сте настроили увеличаване на animateHeightIds на децата от най-високо ниво.

По-напреднали примери:

  • Преместване на околното съдържание с AnimateHeightContainer
  • Пример за акордеон

ЗАБЕЛЕЖКА: Ако използвате този метод за анимиране на височина и движещо се околно съдържание, трябва да добавите сумата на прехода към височината на вашата страница.

Заключение

Може би сте забелязали в тази статия, че всъщност не анимираме височината - и я наричаме погрешно. Разбира се, напълно прав. Твърдо вярвам обаче, че това, което наричаме, няма значение. Важното е, че постигаме желания ефект с възможно най-ниски разходи за изпълнение.

Въпреки че мисля, че намерих начин, който е по-добър от директното анимиране на свойството височина, не твърдя, че съм изобретил или помислил по друг начин нещо, което не е било открито преди. Нито съдя. Може би анимирането на височина работи за вас във вашия сценарий - Няма проблем.

All I want is to enable and simplify effects that we all need to do, but sometimes incur costs that are difficult to bear. At the very least, I want to spark a discussion that is worth having. How can we improve the internet for everyone?