React Spring
React Spring - это основанная на физике у пругости (spring - пружина, рессора) библиотека для реализации анимации в
React-приложениях
. Разработчики данной библиотеки утверждают, что она способна удовлетворить большую часть потребностей, возникающих при разработке пользовательских интерфейсов.
Установка
yarn add react-spring
# или
npm i react-spring
Хуки
Основы
В настоящее время react-spring
предоставляет 5 хуков (примитивов для анимации):
useSpring
- простая пружина, перемещающая данные из точки "а" в точку "б"useSprings
- несколько пружин, для списков, где каждая пружина перемещает данные...useTrail
- несколько пружин с общим набором данных, одна пружина следует за другойuseTransition
- для реализации переходов при монтировании/размонтировании компонентов (например, для элементов списка, которые добавляются/удаляются/обновляются). Обратите внимание: частью разрабатываемого в настоящее время командойReact
конкуретного режима является хукuseTransition
, предназначенный для обработки переходов между состояниями компонента. Как будет решена эта коллизия, покажет времяuseChain
- для постановки в очередь или выстраивания в цепочку нескольких анимаций
Самым простым является хук useSpring
import { useSpring, animated } from 'react-spring'
function App() {
const props = useSpring({ opacity: 0, from: { opacity: 1 } })
return <animated.div style={props}>Помогите! Я исчезаю</animated.div>
}
animated
- это специальная фабрика, библиотека для выполнения анимации за пределами React
(по соображениям производительности). Что делает animated
? Она расширяет нативные элементы, чтобы они могли принимать анимированные значения (как styled
в стилизованных компонентах).
Пружина анимирует значение от одного состояния до другого. Обновления суммируются, пружина запоминает переданные значения. Полученные пропы не являются статическими, они автоматически обновляются.
animated
способна анимировать не только HTML-элементы, как в приведенном выше примере, но также React-компоненты, нативные компоненты, стилизованные компоненты и т.д.
// компонент React
const MyAnimatedComponent = animated(MyComponent)
// React Native
const AnimatedView = animated(View)
// styled-components, emotion и т.д.
const AnimatedListItem = styled(animated.li)`
...
`
Передаваемые значения - это не совсем стили
Их можно использовать так:
const props = useSpring({
opacity: 1,
from: { opacity: 0 }
})
return <animated.h1 style={props}>Привет!</animated.h1>
Можно анимировать атрибуты:
const props = useSpring({
x: 100,
from: { x: 0 }
})
return (
<animated.svg strokeDashoffset={props.x}>
<path d="...">
</animated.svg>
)
Можно анимировать текстовое содержимое:
const props = useSpring({
number: 1,
from: {
x: 0
}
})
return <animated.span>{props.number}</animated.span>
Можно анимировать прокрутку страницы:
const props = useSpring({
scroll: 100,
from: {
scroll: 0
}
})
return <animated.div scrollTop={props.scroll} />
Или генерировать пропы компонента:
const AnimatedCircle = animate(Circle)
const props = useSpring({
value: 100,
from: {
value: 0
}
})
return <AnimatedCircle percent={props.value} />
Передняя интерполяция (up-front interpolation)
Обратите внимание: интерполяция (здесь) - это переход значения из одного состояния в другое через одно или более промежуточных состояний.
Пружины умеют работать не только с числами, но и с:
- цветами (названия, rgb, rgba, hsl, hsla, градиенты)
- абсолютными значениями (см, мм, дюймы, пиксели, пункты, пики)
- относительными значениями (em, ex, ch, rem, vw, vh, vmin, vmax, %)
- углами (deg, rad, grad, turn)
- единицами flex и grid (fr и т.д.)
- всеми HTML-атрибутами
- путями SVG (до тех пор, пока совпадает количество точек, в противном случае, следует использовать кастомную интерполяцию)
- массивами
- строковыми паттернами (transform, border, boxShadow и т.п.)
- не анимируемыми строковыми значениями (visibility, pointerEvents и др.)
- scrollTop/scrollLeft
const props = useSpring({
vector: [0, 10, 30],
display: 'block',
padding: 20,
background: 'linear-gradient(to right, #009fff, #ec2f4b)',
transform: 'translate3d(0px, 0, 0) scale(1) rotateX(0deg)',
boxShadow: '0px 10px 20px 0px rgba(0, 0, 0, 0.4)',
borderBottom: '10px solid #2d3747',
shape: 'M20,20 L20,380 L380,380 L380,20 L20,20 Z',
textShadow: '0px 5px 15px rgba(255, 255, 255, 0.5)'
})
Интерполяция представления (view interpolation)
В случае, когда нам требуется зафиксировать или экстраполировать значения, можно воспользоваться функцией interpolate
, которую содержит каждое анимированное значение. Данная функция принимает функцию или объект с диапазоном значений. Из интерполяций можно формировать цепочки, позволяющие подключать одни вычисления к другим или повторно использовать произведенные ранее вычисления.
Причина, по которой интерполяцию лучше выполнять в представлении, а не в пружине, заключается в том, что интерполяция представления работает немного быстрее и занимает меньше места
import { useSpring, animated, interpolate } from 'react-spring'
const { o, xyz, color } = useSpring({
from: {
o: 0,
xyz: [0, 0, 0],
color: 'red'
}
o: 1,
xyz: [10, 20, 5],
color: 'green'
})
return (
<animated.div
style={{
// По-возможности, всегда используйте обычные анимированные значения,
// когда они просто передаются
color,
// Пока не потребуется их интерполировать
background: o.interpolate(o => `rgba(210, 57, 77, ${o})`),
// Что также работает с массивами
transform: xyz.interpolate((x, y, z) => `translate3d(${x}px, ${y}px, ${z}px)`),
// Для комбинации нескольких значений используется утилита `interpolate`
border: interpolate([o, color], (o, c) => `${o * 10}px solid ${c}`),
// Можно создавать диапа зоны и даже цепочки из нескольких интерполяций
padding: o.interpolate({ range: [0, 0.5, 1], output: [0, 0, 10] }).interpolate(o => `${o}%`),
// Разрешена интерполяция строк
borderColor: o.interpolate({ range: [0, 1], output: ['red', '#ffaabb'] }),
// Существует сокращенный синтаксис для диапазонов без настроек
opacity: o.interpolate([0.1, 0.2, 0.6, 1], [1, 0.1, 0.5, 1])
}}
>
{o.interpolate(n => n.toFixed(2))} /* интерполяция текста */
</animated.div>
)
Заметки
Анимирование значения auto
Одним из отличий хуков от пропов для рендеринга является то, что хуки "не знают" о представлении. Поэтому хуки не умеют работать со значением auto
. Это может показаться недостатком, однако, вычисление размера элемента с помощью таких хуков как useResizeAware
или useMeasure
никогда не было таким простым, как сейчас.
const [ref, {height}] = useMeasure()
const props = useSpring({ height })
return (
<animated.div style={{ overflow: 'hidden', ...props }}>
<div ref={ref}>Контент</div>
</animated.div>
)
Имитация ключевых кадров
/*
0% { transform: scale(1); }
25% { transform: scale(.97); }
35% { transform: scale(.9); }
45% { transform: scale(1.1); }
55% { transform: scale(.9); }
65% { transform: scale(1.1); }
75% { transform: scale(1.03); }
100% { transform: scale(1); }
*/
const props = useSpring({ x: state ? 1 : 0 })
return (
<animated.div
style={{
transfrom: props.x
.interpolate({
range: [0, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 1],
output: [1, 0.97, 0.9, 1.1, 0.9, 1.1, 1.03, 1]
})
.interpolate(x => `scale(${x})`)
}}
/>
)
Общий интерфейс
Настройки
Для настройки анимации используется объект config
, передаваемый useSpring
и другим хукам
useSpring({ config: { duration: 250 }, ... })
Свойство | Значение по умолчанию | Описание |
---|---|---|
mass (масса) | 1 | вес пружины |
tension (натяжение) | 170 | энергичность растяжения/сжатия пружины |
friction (трение) | 26 | сопротивление пружины |
clamp (ограничение) | false | если true , тогда пружина останавливается при достижении границы |
precision (точность) | 0.01 | точность |
velocity (скорость) | 0 | начальная скорость |
duration (продолжительность) | undefined | установление значения больше 0 приводит к переключению анимации с основанной на упругости на основанную на продолжительности |
easing (характер движения) | t => t | по умолчанию является линейным |
Пресеты
Существует набор общих пресетов, охватывающих некоторые распространенные случаи
import { ..., config } from 'react-spring'
useSpring({ ..., config: config.default })
Свойство | Значение |
---|---|
config.defaut | { mass: 1, tension: 170, friction: 26 } |
config.gentle | { mass: 1, tension: 120, friction: 14 } |
config.wobbly | { mass: 1, tension: 180, friction: 12 } |
config.stiff | { mass: 1, tension: 210, friction: 20 } |
config.slow | { mass: 1, tension: 280, friction: 60 } |
config.molasses | { mass: 1, tension: 280, friction: 120 } |
Свойства
useSpring({ from: {...}, to: {...}, delay: 100, onRest: () => ... })
Все примитивы наследуют следующие свойства (некоторые могут предоставлять дополнительный функционал):
Свойство | Тип | Описание |
---|---|---|
from | obj | Базовые значение, опционально |
to | obj/fn/arr(obj) | Конечные значения |
delay | num/fn | Задержка в мс перед началом анимации. Может быть функцией для отдельных ключей: key => delay |
immediate | bool/fn | Если true , анимация отключается. Может быть функцией для отдельных ключей |
config | obj/fn | Настройки пружины (масса, натяжение, трение и т.д.). Может быть функцией для отдельных ключей |
reset | bool | Если true , анимация начинается с самого начала |
reverse | bool | Если true , from и to меняются местами. Это имеет смысл только при совместном использовании с reset |
onStart | fn | Колбек, вызываемый в начала анимации |
onRest | fn | Колбек, вызываемый после остановки всех анимаций |
onFrame | fn | Покадровый (frame by frame) колбек, первый передаваемый аргумент является значением анимации |
Интерполяция
value.interpolate
принимает объект следующей формы:
Свойство | Значение по умолчанию | Описание |
---|---|---|
extrapolateLeft | extend | Левая экстраполяция. Возможные значения: identity/clamp/extend |
extrapolateRight | extend | Правая экстраполяция. Возможные значения: identity/clamp/extend |
extrapolate | extend | Сокращенный синтаксис для установки левой и правой экстраполяций |
range | [0, 1] | Массив входных значений |
output | undefined | Массив соответствующих (mapped) выходных значений |
map | undefined | Фильтр, применяемый к входному значению |
Сокращение для диапазона: value.interpolate([...inputRange], [...outputRange])
. Может быть функцией: value.interpolate(v => ...)
useSpring
import { useSpring, animated } from 'react-spring'
Преобразует обычные значения в анимируемые.
Повторный рендеринг компонента с изменившимися пропами приводит к обновлению анимации
const props = useSpring({ opacity: toggle ? 1 : 0 })
useSpring
возвращает пропы (props) и две функции: для обновления анимации (set) и ее остановки (stop)
const [props, set, stop] = useSpring(() => ({ opacity: 1 }))
// Обновляем пружину новыми пропами
set({ opacity: toggle ? 1 : 0 })
// Останавливаем анимацию
stop()
Для запуска анимации props
передается в атрибут style
компонента
return <animated.div style={props}>Я появляюсь и исчезаю</animated.div>
Заметки
Сокращение для пропа to
Любое неопознанное свойство становится свойством to
, например, opacity: 1
становится to: { opacity: 1 }
// Это
const props = useSpring({ opacity: 1, color: 'red' })
// является сокращением для этого
const props = useSpring({ to: { opacity: 1, color: 'red' } })