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

Эта проблема

Давайте начнем с создания этой простой анимации, в которой div с размером в пикселях расширяется, чтобы заполнить всю ширину и высоту области просмотра, когда мы щелкаем по нему:

Чтобы создать эту анимацию, мы будем использовать useSpring хук из react-spring пакета и установить ширину и высоту поля на 200 пикселей, когда он не расширен, и на 100vh и 100vw, когда он развернут. Мы также удалим радиус границы 10 пикселей при раскрытии поля:

Результат будет таким:

Как мы видим, анимация border-radius работает, но вместо этого прямоугольник становится меньше. Это почему?

Чтобы понять проблему, нам нужно посмотреть, как react-spring (и большинство библиотек анимации React) обрабатывает анимацию между юнитами. Когда мы передаем значения ширины и высоты в виде строк, react-spring будет анализировать числовые значения из значений 'from' и 'to', брать единицу измерения из значения 'from' и полностью игнорировать единицу измерения значения 'to':

В нашем примере начальное состояние блока свернуто, а высота блока основана на пикселях, поэтому, когда react-spring начинает его анимировать, он будет использовать «пиксели» как единицу. Если вместо этого начальное состояние было расширено, а высота была основана на области просмотра, тогда анимация использовала бы 'vh' в качестве единицы и вместо этого выполнялась бы от 100vh до 200vh.

Анимация border-radius отлично работает, потому что if использует пиксели как для развернутого, так и для свернутого состояния.

Боковое примечание: единственная библиотека, которую я обнаружил, которая без дополнительных настроек правильно обрабатывает анимацию между юнитами, - это framer-motion. У меня есть вводная статья о фреймер-моушн, ​​и вы также можете подписаться на мой предстоящий фреймерский курс.

Решение

Чтобы решить эту проблему, нам нужно убедиться, что и начальное, и целевое значение используют одни и те же единицы измерения. Мы можем легко преобразовать значения на основе области просмотра в пиксели с помощью этих простых вычислений:

Теперь вместо использования значений на основе области просмотра мы будем использовать вспомогательные функции для установки ширины и высоты поля:

Это решает проблему лишь частично, потому что, если мы изменим размер окна браузера после запуска анимации, мы обнаружим другую проблему - окно больше не подстраивается под размер области просмотра, поскольку теперь оно имеет размер в пикселях:

Мы можем решить эту проблему, вернув размер окна к значениям на основе области просмотра после завершения анимации. Прежде всего, мы будем использовать useRef hook для хранения ссылки на фактический узел DOM нашего бокса. Во-вторых, react-spring предоставляет удобный обратный вызов onRest, который запускается в конце каждой анимации, поэтому мы можем использовать его, чтобы проверить, анимировали ли мы в развернутом состоянии, и если да, мы напрямую установим ширину и высоту окна.

С этой настройкой анимация работает нормально - она ​​использует значения пикселей во время анимации и устанавливает размеры блока на основе размера окна просмотра после завершения, поэтому блок остается отзывчивым, даже если мы впоследствии изменим размер браузера.

Вы можете найти рабочую демонстрацию CodeSandbox здесь.

Заключение

Библиотеки анимации, такие как react-spring, дают нам больший контроль над анимацией по сравнению с анимацией CSS, но у них также есть недостатки. Анимация значений между единицами измерения является одним из них, и это требует от нас дополнительной работы, чтобы убедиться, что наша анимация работает плавно и остается отзывчивой.

Первоначально опубликовано на https://konstantinlebedev.com.