Анімація висоти - правильний шлях

Будемо чесними. Анімація висоти може бути величезним болем. Для мене це була постійна боротьба між бажанням мати гарну анімацію та не бажаючим платити величезні витрати на продуктивність, пов’язані з анімацією висоти. Зараз - все готово.

Все почалося, коли я працював над своїм допоміжним проектом - розробником резюме, де ви можете ділитися посиланнями на своє резюме, які активні лише певний період часу.

Я хотів отримати гарну анімацію для всіх розділів резюме, і я створив компонент реакції, який виконував анімацію. Однак незабаром я виявив, що цей компонент абсолютно знищив продуктивність на нижчих пристроях та в деяких браузерах. Чорт, навіть мій висококласний Macbook pro намагався зберегти плавну частоту кадрів в секунду на анімації.

Причиною цього, звичайно, є те, що анімація властивості height у CSS змушує браузер виконувати дорогі операції макетування та фарбування на кожному кадрі. Існує фантастичний розділ про рендеринг продуктивності в Google Web Fundamentals, я пропоную вам перевірити це.

Коротше, хоча - коли ви змінюєте геометричну властивість у css, браузер повинен коригувати та проводити обчислення того, як ця зміна впливає на макет сторінки, тоді йому доведеться повторно відображати сторінку на кроці, який називається paint.

Чому ми взагалі повинні дбати про продуктивність?

Це може бути спокусливим ігнорувати продуктивність. Це не мудро, але може бути спокусливим. З точки зору бізнесу ви економите багато часу, який інакше можна витратити на створення нових функцій.

Однак ефективність може безпосередньо вплинути на ваш підсумок. Яка користь від побудови багатьох функцій, якщо їх ніхто не використовує? Багато досліджень, проведених Amazon і Google, показують, що це правда. Ефективність безпосередньо пов’язана з використанням програм та нижчим рівнем доходу.

Інша сторона продуктивності не менш важлива. Ми, як розробники, несемо відповідальність за те, щоб Інтернет залишався доступним для всіх - ми робимо це, оскільки це правильно. Оскільки Інтернет призначений не тільки для вас і мене, він призначений для всіх.

Як свідчить чудова стаття Адді Османі, пристроям низького класу потрібно значно більше часу для синтаксичного аналізу та виконання 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?