Хабрахабр

Анимации в мире состояний

Многие уже научились строить чистые интерфейсы и писать «undo-redo» в несколько строчек. Но как быть с анимациями? Часто их обходят стороной, и они не всегда вписываются в подход (state) ↦ DOM. Есть отличные решения вроде React Motion, но что если вам нужно делать сложные анимации или работать с Canvas, используя физический движок?

А также о том, как «запускать» анимации в Redux-приложениях. В нашем тексте рассказывается, как работать с анимациям в React-приложениях, и сравнивается несколько подходов (D3, React-Motion, «грязные компоненты»). Прилагаем заодно видеозапись этого доклада: Материал основан на расшифровке доклада Алексея Тактарова с нашей декабрьской конференции HolyJS 2017 Moscow.

Осторожно, трафик: под катом много картинок и гифок (сами понимаете, материал про анимации).

Я хотел бы начать с истории. Если бы вы оказались в Древнем Риме примерно в первом веке до нашей эры, то могли бы встретить Витрувия.

Он написал 10 томов об архитектуре. Витрувий — это один из знаменитейших архитекторов того времени. Но самое интересное — в одной из книг он вывел три главных качества, которыми должна обладать хорошая архитектура: прочность, польза и красота. Его трудами вдохновлялись многие.

К чему все это? Мне кажется, эту триаду можно найти и в дизайне вещей, которыми мы пользуемся. Делаем вещи, которыми пользуются люди. Мы — разработчики — пишем приложения, сайты. Ведь приложения, которые мы делаем, должны решать конкретные задачи людей. Поэтому мы тоже, как мне кажется, должны взглянуть на те правила, которые были выведены две тысячи лет назад.

Это ключевой момент. Я думаю, что вся суть в обратной связи. И в какой-то степени обратная связь — это анимация, переходы между состояниями.

Далее я буду говорить про анимацию в stateful-приложениях на примере React.

Паттерны анимаций на примере демо из реальных проектов

Прежде чем мы поговорим про React, давайте подумаем, как в целом работают анимации в браузерах, и что это такое.

Наивно полагать, что их можно делать, например, с помощью setinterval или settimeout. И здесь есть два заблуждения. В идеальном мире анимации должны работать плавно.

Заблуждение №1

Мы не правы. Потому что мы не можем использовать setTimeout для анимации. Ведь setTimeout не будет гарантировать того, что ваша функция действительно сработает в указанный промежуток времени. Это может приводить к таким эффектам, как наложение кадров. То есть вы будете думать, что ваша анимация придет через 16 миллисекунд, что примерно соответствует 60 кадрам в секунду, но на самом деле сработает больше, и этот долг будет накапливаться и накапливаться.

Она гарантирует, что callback отработает в удобное для планировщика браузера время. К счастью, есть функция, которая позволяет бороться с этой проблемой — requestAnimationFrame. Если вы захотите использовать requestAnimationFrame, я настоятельно рекомендую посмотреть, на какие браузеры вы ориентируетесь. При этом хочу заметить, что она также может работать через неравные промежутки времени.

То есть вы объявите функцию, которая будет срабатывать в тот момент, когда должна происходить анимация. Если вы захотите использовать requestAnimationFrame в своих проектах, то вы будете применять что-то вроде этого паттерна. Потом вы вызовите requestAnimationFrame — это будет означать, что мы выполним нашу функцию в нужное время.

В принципе в конце функции мы могли бы вызывать планирование следующего кадра, но таким паттерном является вызов requestAnimationFrame для следующего кадра в самом начале. Дальше интересный момент. Может она, например, выбросит исключение. Так как никто не знает, что может произойти во время выполнения функции. Поэтому лучше сразу планировать следующий кадр в самом начале.

// Or use a polyfill:
// import requestAnimationFrame from 'raf' const = window const animate = () => { requestAnimationFrame(animate) // Perform an animation step x += velocity
} // Fire it up
requestAnimationFrame(animate)

requestAnimationFrame — незаменимый инструмент для анимаций в браузере.

И здесь мы с вами столкнулись с заблуждением №2. Далее вы хотите анимировать какое-то свойство с течением времени, например, брать координату некоторого объекта и увеличивать его на какую-то константу, которая по сути является скоростью.

Заблуждение №2

Скорость постоянная!


Посмотреть демо

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

requestAnimationFrame(timestamp => { // DOMHighResTimeStamp // timestamp ~> 30485.84100000153
})

rAF передает в коллбек временную метку с точностью пять микросекунд.

Вообще вы можете рассчитывать, что это будет некоторый double (если используется полифил, то вряд ли), который содержит миллисекунды до запятой, и микросекунды — после.

Эту метку мы можем использовать, чтобы посчитать разницу между этим и предыдущим вызовом функции.

const animate = timestamp => { const delta = timestamp - prevTimestamp // Note, it's a function now! x += velocity(delta) requestAnimationFrame(animate)
}

Важно считать разницу во времени между вызовами и анимировать значение пропорционально дельте!

Теперь ту дельту, которая у нас получилась, мы передаем внутрь функции. Поэтому мы можем ввести некую дельту, которая является разницей текущей метки и предыдущей за вызов, и дальше анимируем нашу переменную, но только при этом она становится функцией. И мы считаем скорость пропорциональной.

Давайте посмотрим, что из этого получится.


Посмотреть демо

Можно заметить, что вызовы все равно происходят не через равные промежутки времени. Слева — идеальная анимация, справа — с нашим адаптивным алгоритмом. То есть мы гарантируем, что она будет выглядеть так же. И анимация справа может быть не такой плавной, как слева — но при этом она имеет ту же форму. Выполнять анимацию в заданный промежуток времени — не нормально, а вот пропускать кадры в анимации — совершенно нормально. Это называется пропуск кадров. Поэтому, используя подход дельты, можно сделать высокопроизводительную анимацию.

Небольшой пример

На видео он начинается с 13:09.

Потому что на нем можно делать любую логику для анимации. requestAnimationFrame — отличный инструмент для произвольных анимаций в браузере.


Посмотреть демо

Это симуляция движения птиц во время перелета. В примере я вывел частицы, которые движутся по некоторому закону. Скорее всего, вы бы завели функцию tick, которая вызывалась бы в момент просчета анимации. Как бы вы делали такой пример в браузере? Она делала бы две вещи: считала физику и потом перерисовывала.

const redraw = _ => { points.forEach(point => { // make sure `will-change: transform` is set point.element.style.transform = ` translate3d(${point.x}px, ${point.y}px, 0.0px) rotate(${point.angle}rad)` })
} const tick = ts => { _lastRaf = requestAnimationFrame(tick) physicsStep(delta) redraw(delta)
}

Про перерисовку очень интересно, потому что если вы будете использовать div’ы, как я в примере, то для того, чтобы максимально быстро анимировать их в браузерах, необходимо использовать transform. А вот если бы вы использовали margin, padding или абсолютное позиционирование, то у вас бы ничего не вышло, а если бы работало, то очень медленно.

Это будет гарантировать, что квадратики будут находиться сразу в отдельном слое браузера и потом — композироваться в один общий. Очень важно, чтобы у этих элементов стояло свойство ‘will-change: transform’. Потом мы пробегаемся по всем точкам и выставляем то, каким будет поворот и позиция точки на экране. Так удастся достичь максимальной производительности.

Теперь про stateful-приложения.

И, скорее всего, используют подход, который называется Immutable UI. Я уверен, что многие работают со stateful-приложениями, даже не подозревая об этом.

Это когда у вас есть некоторое состояние, и вы его однозначно транслируете в элементы на странице. Что такое Immutable UI? То есть вы вызываете функцию render, после чего данные, которые у вас есть, транслируются в элементы, и вы получаете состояние на странице. Обычно это просто render. Но потом вы начинаете совершать какие-то действия на странице, водить мышкой или нажимать на клавиши на клавиатуре, тем самым создавая события. Все здорово!

То есть по сути наше приложение — это цепочка состояний и соответствующих им состояний элементов на странице. Эти события в вашем приложении приводят к тому, что меняются состояния, а также элементы на странице. Поэтому возникает вопрос, что в этом случае делать с анимациями. Но проблема в том, что если вы работаете в react или в angular, то, скорее всего, для вас, как для разработчика, это скрыто, потому что когда вы обновляете состояние, то видите на экране перерисованное новое состояние.

Надо отметить, что такой подход Immutable UI имеет много плюсов, потому что вы можете легко его тестировать и делать совершенно безумные вещи.

Все это очень здорово. В примере под спойлером я сделал подобие to do листа ( с 18:55, вставить спойлер).
Я менял состояния его пунктов, потом вывел все их одновременно и мог путешествовать во времени назад относительно своих действий.

Самый простой способ сделать анимацию в Immutable-приложениях

Это css transitions. Давайте теперь посмотрим на самый простой способ создания анимации в Immutable-приложениях.

// CSS property
// transition: transform is ease; // Conditional state change
<div className ={isVisible ? 'is-visible' : 'is-hidden'} /> // Direct style manipulation
<div style={{ transform: `translate(${scale})` }} />

CSS анимации в React работают из коробки. Свойство transition + смена состояния = анимация.

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

Ну и можно вручную менять стили на элементе. Один из паттернов — замена одного класса на другой. С этим все понятно.

<div> <div style="transform: translate(42.00px, 165.00px)" /> ...
</div


Посмотреть демо

У нас есть набор точек, по определенному закону я перевожу их в координаты и отрисовываю. Я сделал пример, чтобы продемонстрировать, как работает CSS transitions в React-приложениях. Если я полностью поменяю все данные, изображение изменится мгновенно, и браузер сам дорисует переход. Это просто массив элементов с разными свойствами.

Например, если вы запустите одну анимацию и в тот же момент — другую, то ничего не сломается, потому что браузер знает как остановиться и перейти к новому состоянию. В ряде случаев CSS transitions ведут себя не очень натурально. Но настоящие программы должны работать не так.

И на ней я сделал вторую демку (21:25). К счастью, в случае с React есть библиотека React-Motion. Все происходит так же, а сама библиотека делает переходы уже вручную. Мы взяли тот же самый пример: есть массив точек, мы меняем их состояние, но у нас появляется обертка, которая называется Motion.

<div> <Motion style={{x: spring(42.00, y: spring(165.00))}}> <div style="transform: translate(42.00px, 165.00px)" /> </Motion> ...
</div>


Посмотреть демо

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

В React это выглядит вот так.

<Motion> {interpolated => <div style={{ opacity: interpolated.x }} /> }
</Motion>

В React-Motion используется отличный паттерн function-as-a-prop. Если у вас есть какой-либо компонент и у него есть тело, его children, то эти children не обязательно должны быть элементами. Они могут быть любым типом данных, в том числе функцией, которая принимает некоторое состояние и возвращает элементы. Такая запись немного пугает новичков, но это работает очень здорово. Вы можете думать, что React-Motion лезет в DOM, меняет некоторые свойства. На самом деле это не так.

То есть каждый кадр — это новое состояние, новый render. Это тот самый requestAnimationFrame, про который мы говорили в самом начале, и на каждом шаге анимации мы просто обновляем их состояние. Удивительно, но это работает.


Посмотреть демо

Один совет — не используйте React-Motion везде.

Пружинные анимации не ограничиваются по времени, то есть нужно сделать анимацию, которая будет запускаться и работать 10 секунд, то React-Motion тут не помощник. К сожалению, он подходит не для всех кейсов.

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

Все равно ничего не может сравниться с ручной анимацией, то есть. И, наконец, производительность. Поэтому в некоторых случаях React-Motion ведет себя «прожорливо», хоть и работает неплохо. когда мы лезем в элемент и меняем его transform.

«Грязные» анимации

Грязные анимации — анимации, в которых не всегда все можно построить на состояниях.

class Dialog extends Component{ componentDidMount(){ const node = findDOMNode(this) // Or $.animate, anime.js, GSAP, D3 ... Velocity(node, {scale: 1.5}, {duration: 1000}) } render(){ ... }
}

Паттерн «анимации на входе» работает через хук componentDidMount и прямой доступ к элементу.

Вы, наверное, встречались с этим, когда вам требовалось показать или скрыть диалоговое окно. Давайте рассмотрим это на примере диалогового окна. Но здесь существует проблема: диалоговое окно может уйти из DOM раньше, чем закончится анимация. Чаще всего это делалось при помощи паттерна componentDidMount, то есть в React есть такой хук, который вызывается после того, как компонент был добавлен в DOM. Поэтому за этим тоже нужно следить.

Такая возможность есть при использовании библиотек Velocity или jQuery animate. Если вы работаете с грязными анимациями входа, то есть анимируете что-то на входе, наилучший совет — сохраняйте дескриптор анимации и потом отменяйте ее, если компонент уходит из DOM раньше времени.

class Dialog extends Component{ componentDidMount(){ const node = findDOMNode(this) // animate returns a cancellable // promise-like object this._anim = animate(node, { ... }) } componentWillUnmount(){ this._anim && this._anim.cancel() }
}

То есть тут можно извлечь компонент до того, как закончится анимация.


Посмотреть демо

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

<div> {this.state.showDialog && <Dialog />}
</div>

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

<Animated> {this.state.showDialog && <Dialog />}
</Animated>

Когда внутри появляются дочерние элементы, мы запускаем анимацию входа, а как только они пропадают, мы делаем анимацию выхода и смотрим на краевые случаи.

У нас есть четыре состояния, в которых может находиться компонент в определенный момент: когда он анимируется, когда на экране, когда он выходит и когда уже вышел. Если мы представим, как мог бы работать компонент, то получим вот такую карту. У нас уже нет этих children, которые нам передали. С первыми двумя состояниями все понятно (entering, entered), но вопрос, что делать с состоянием (exitting), когда компонент выходит? Поэтому здесь можно использовать такой трюк, который называется ghostChildren, то есть оставить на элементы и компоненты, пока не сработает анимация. Мы должны что-то нарисовать.

const element = <Dialog size="medium" />
// => { type: Dialog, props: { size: 'medium' }, ... }
const element = React.createElement(Dialog, { size: 'medium'})

Что скрывается за JSX?

В целом код получается не очень приятным. Когда нам нужно сделать анимацию выхода, то мы берем children, сохраняем, добавляем в state и делаем анимацию выхода.

В этой функции мы смотрим, в какое состояние мы хотим перейти и какие дополнительные опции принимаем. Когда мы получаем новые children, то можно увидеть, что они поменялись. Самое главное, что, используя ссылку на компонент, мы можем вызвать у него функцию анимации выхода и сделать правильный переход.

componentWillReceiveProps(nextProps){ // Exit transition if(this.props.children && !nextProps.children){ return this.transitionState(st.EXITING, {children: this.props.children}) }
} transitionState(transitionTo, opt = {}){ // .. FSM logic .. // Wait for `this._content.animateExit()`
}

Компонент-хелпер Animated с поддержкой анимацией выхода.

Давайте посмотрим, что получилось (на видео с 31:33).


Посмотреть демо

Переходы будут анимироваться не до конца и вовремя уходить с экрана. Интересно, что если вы будете менять состояния слишком быстро, анимация будет вести себя корректно.

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

import Transition from 'react-transition-group/Transition' // `state` is 'entered', 'entering', 'exited' etc.
<Transition in={isVisible} timeout={duration}> {state => <ModalDialog animationState={state} />}
</Transition>

React-transition-group@2.0 представляет собой декларативный компонент для анимаций входа/выхода.

Используя так называемые грязные компоненты, можно делать сложные компоненты, в которых происходят изменения состояний.


Посмотреть демо

По сути, это большой компонент, в который автоматически приходят состояния из сети и с websocket’ов. В очередном примере (на видео с 32:57) я сделал гистограмму, на которой менялись значения. В данном случае я использовал D3. Необходимо, чтобы анимация сама выполнялась, поэтому мой компонент выглядит как обычный, но внутри анимирует состояния при помощи грязных анимаций.

Иногда невозможно получить доступ к элементу и нужно использовать Web API, так называемые грязные компоненты — это Canvas и так далее В таком случае можно применить паттерн — перехват ответственности.

render(){ return <canvas />
} // Render only once!
shouldComponentUpdate() { return false } componentWillReceiveProps(nextProps){ if(this.props.color != nextProps.color){ // Animate on canvas... }
}

С помощью хуков можно полностью перехватить ответственность за рендер. Например, для работы с Canvas, WebGL, WebAudio и так далее.

Далее вы говорите, что не будете рендерить этот компонент, так как перехватываете ответственность и в специальной функции возвращаете false. Итак, вы делаете рендер один раз. Звучит просто, но на практике получается, что анимировать не всегда удобно. Далее в хуке componentWillReceiveProps вы смотрите, какие свойства пришли, поменялись ли они, и выполняете нужную анимацию. Давайте посмотрим, почему.

Это икосаэдр. Я сделал вот такой компонент на WebGL. И внутри функции componentWillReceiveProps я сравниваю слепок и делаю необходимые трансформации. Я сверху через два свойства передаю ему, = как его крутить по вертикали и горизонтали.


Посмотреть демо

Но есть трюк под названием контроллер, который позволяет сделать плавную анимацию. Это вращение не совсем натуральное, оно выглядит грубо и сопровождается рывками.

В том, что раньше мы смотрели, что приходит сверху, и обновляли внутреннее состояние, поэтому разворот был мгновенным. В чем разница? Контроллер — понятие из теории управления. А контроллер работает немного по-другому. Эта область, которая управляет руками робота. В нашем случае это P-контроллер, частный случай PID-контроллера.

В нашем случае это простой контроллер, и его действия основаны на следующем эффекте.

// Limit delta to avoid divergence
const delta = Math.min(100.0, ts - prevTs)
const P = 0.001 + delta this.x = P + (this.target - x)

P-контроллер удобен для плавных неограниченных по времени анимаций.

Мы смотрим, насколько мы далеко от нужного места, умножаем на коэффициент и двигаемся на эту точку. У нас есть строка, есть значение (this.x) и нам нужно перевести его в target. Я хочу заметить, если вы используете requestAnimationFrame и контроллеры в анимации, то лучше всего, если вы будете добавлять дельту.  В целом формула такая же, как и для закона Гука, и для пружин. Причем в данном случае я ее ограничил, потому что, если вы переключитесь на другую вкладку браузера, а потом вернетесь назад, то у вас будет очень большая дельта. Ту, которую вы получили между вызовами requestAnimationFrame. Поэтому мы ограничиваем ее, умножаем на какую-то константу и используем. Это приведет к тому, что у вас будут очень большие значения, и пружина сломается.

Используя паттерн «перехват ответственности», можно работать и с физикой.


Посмотреть демо

Если вы делаете грязные компоненты, то вы должны следить, чтобы:

  1. Ваши компоненты имели чистый интерфейс.
  2. Сайд-эффекты были спрятаны

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

import { Actuator, actuate } from 'redux-actuator' // Inside the component
<Actuator on={{ animateBadge: this.animateBadge }} /> // Where the business logic is
store.dispatch(actuate('animateBadge'))
store.dispatch(actuate('highlighUser', { id: 1 }))

Паттерн удобно использовать в Redux-приложениях, где глобальный стейт — единственный способ коммуникации.

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

Там есть такой актуатор, который позволяет вызывать события внутри компонентов. Я опубликовал небольшую open-source утилиту — redux-actuator.


Посмотреть демо

Дело в том, что мы берем некий ключ в нашем состоянии и меняем его на другой. Как это выглядит, можно посмотреть на видео с 40:27. В случае с актуатором я поступаю следующим образом: беру некий id event’a и делаю его из текущего времени и счетчика для того, чтобы избежать коллизий. То есть нам нужно сделать так, чтобы состояние действительно поменялось. И таким образом можно вызывать анимацию.

Скажу так, анимации в коде почти всегда будут выглядеть некрасиво.
Посмотреть демо
У вас может возникнуть вопрос, как делать сложные анимации (пример можно посмотреть на видео с 41:27). Мы же знаем, как делать анимации выхода, поэтому трюк в том, чтобы правильно разделить ответственность. Главное — сделать правильную декомпозицию. Оставляем его на экране, пока идет анимация, и потом применяем технику, которая называется FLIP. В этом случае мы берем слой, который уходит, то есть тот, в котором находятся item’ы. FLIP — это когда вы берете элемент, сразу рисуете то, что хотите получить, как будто мгновенно переходите в роут с превью. В моем примере не совсем FLIP, но принцип тот же. Потом, когда анимация сработала и этот элемент появляется под другим, вы просто подменяете его и убираете его из DOM. Первый роут делаете прозрачным, а второй позиционируете, ставите в DOM, запускаете анимацию. Главное — сделать правильную декомпозицию. Но, к сожалению, в React сейчас нет простой утилиты для того, чтобы это сделать, но это возможно.

В основном мы работали в рамках одного компонента. Сегодня мы с вами рассмотрели несколько подходов к анимации. Они называются так, потому что полностью работают на состояниях. Сначала мы рассмотрели чистые анимации. Наконец, есть третий вид компонентов, которые полностью перехватывают рендер. Далее идут грязные анимации: они в принципе выглядят как чистые, но внутри используют доступ в DOM. Плюс сложные случаи, которые решаются правильной декомпозицией. Когда вам нужно работать с Canvas, WebGL, вы используете подход перехвата рендера.

В завершение я бы хотел посоветовать доклады по теме анимации, которые можно посмотреть.

Ссылки

Дополнения к оригинальному докладу:

В React, начиная с версии 16.3, метод componentWillReceiveProps становится deprecated, т.к. новый рендерер его не сможет поддерживать. Обычно этот хук использовался для того, чтобы устанавливать state на основе props, которые передают компоненту. Сейчас команда React советует плавно уходить от componentWillReceiveProps в сторону getDerivedStateFromProps. Но проблема в том, что метод теперь статичный, поэтому если он для трансформации props в state еще будет работать, то для перехвата ответственности точно нет.

Подробнее можно прочитать тут.

Например, react-canvas. Сейчас официального решения для этого случая нет, но его поддержка должна появиться очень скоро, поскольку в npm достаточно пакетов, использующих перехват ответственности.

Узнал про этот API уже после доклада. Существует официальный способ запуска high-performant анимаций в React Native. При этом, это совершенно разные по принципу работы компоненты. Он называется <Animated />, как компонент из  примеров в докладе. «бридж». Смысл следующий: чтобы делать производительные анимации в RN, трюк с setState и перерисовкой каждого кадра как в react-native не пройдет — это слишком тяжело для смартфонов, учитывая, что приложения RN работают через т.н. JS и рендер выполняются в отдельном потоке, а результат потом синхронизируется с нативными View (в iOS) уже в других потоках. Т.е. Оборачиваешь свой компонент в <Animated />, говоришь, какие стили должны анимироваться, и соединяешь с анимируемыми свойствами. Поэтому разработчики предоставили такой туннель для low-level анимаций. У такого подхода много ограничений, главный — мы не можем влиять на способ вычисления свойств из JS, их зависимости полностью декларативны, поскольку дерево зависимостей должно быть сериализованным (чтобы отправлять на нижние уровни). Причем свойства могут зависеть друг от друга, есть возможность делать пружинные зависимости, последовательности и т.д. Появились даже компоненты для задания алгебры анимаций: https://github.com/motiz88/animated.macro

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

Надеемся, вам пригодится опыт Алексея. А если вы любите смаковать детали разработки на JS так же, как и мы, наверняка вам будут интересны вот эти доклады на нашей конференции HolyJS 2018 Piter, до которой осталась всего пара недель:

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть