React Native: от простой анимации до интерактивной на скорости 60 FPS

в 10:43, , рубрики: Animated, javascript, native driver, react native, user interactions, анимация, взаимодейтсиве с пользователем, Разработка под android, разработка под iOS

Необходимость в обмене данными между UI и JS процессами в React Native неизбежно сказывается на производительности препятствуя выполнению JavaScript анимации с высокой частотой кадров. Современные библиотеки, такие как Animated, решают эту проблему минимизацией количества сообщений, передаваемых через мост. Следующий шаг — это элементы управления, которые непрерывно реагируют на жесты пользователя. Как мы можем анимировать их со скоростью 60 кадров в секунду?

Пересекая последнюю милю

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

Однако, есть пара вопросов вызывающих беспокойство. Позволит ли мне React Native пересечь последнюю милю? Будет ли моё приложение соответствовать лучшим среди числа разработанных с использованием родных инструментов?

Должен признать, что это беспокойство оправдано. Около года назад мы в wix.com перешли от использования нативных инструментов с раздельной кодовой базой для iOS и Android к React Native. Первые 95% разработки прошли как по маслу. Мы заметили, что продвигаемся почти в 4 раза быстрее прежнего. Но вот оставшиеся 5% оказались немного сложнее. И эти самые 5%, которые я называю последней милей, все еще не так просто реализовать с использованием React Native.

Наша цель, как сообщества, исправить это.

Что делает приложение выдающимся?

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

Плавная анимация со скоростью 60 кадров в секунду — это важная часть оставшихся пяти процентов. Долгое время анимации были большой проблемой в React Native. Этот вопрос был решён с помощью Animated, великолепной библиотеки, которая является частью ядра.

Но давайте посмотрим немного дальше простой анимации — реалистичное, динамическое взаимодействие с пользователем. Такое взаимодействие возникает когда пользователь совершает жест над элементом и элемент непрерывно и физически реалистично реагирует на него.

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

  • Действия над строкой в списке(ListView row actions) — Слева у нас официальное приложение от Apple — Mail App и Inbox для Gmail от Google. По мере того как пользователь сдвигает строку списка, постепенно появляются кнопки возможных действий.
  • Карточки(Swipeable cards) — Вторым слева у нас идет приложение Google Now от Google и Flic от Lifehack Labs с интерфейсом похожим на Tinder. По мере того как пользователь сдвигает карточки, они меняют свой внешний вид и если сдвинуть достаточно сильно, то улетают с экрана.
  • Сворачиваемые области(Collapsible views) — Вторыми справа показаны приложения Airbnb и Cal от Any.DO. Оба этих приложения имеют области, которые пользователь может свернуть в зависимости от состояния. Например переход от фильтров к поиску в Airbnb или переход от отображения месяца к отображению недели в Cal.
  • Выдвигаемые панели(Sliding panels & drawers) — Справа у нас официальная панель уведомлений в iOS и приложение iOS Maps от Apple. Юзер может вытащить эти панели чтоб посмотреть дополнительные элементы управления, которые скрыты в обычном состоянии. Так же как и в популярном выезжающим боковом меню(navigation drawer / side menu).

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

Реализация с использованием JavaScript

Используя React Native мы, естественно, попробуем реализовать эти анимации с использованием JavaScript. Давайте рассмотрим такую реализацию. Вообще-то, первый пример из приведенных выше(ListView row actions) уже реализован в React Native под именем SwipeableRow.

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

_handlePanResponderMove(event: Object, gestureState: Object): void {
  if (this._isSwipingExcessivelyRightFromClosedPosition(gestureState)) {
    return;
  }
  this.props.onSwipeStart();
  if (this._isSwipingRightFromClosed(gestureState)) {
    this._swipeSlowSpeed(gestureState);
  } else {
    this._swipeFullSpeed(gestureState);
  }
},

_isSwipingRightFromClosed(gestureState: Object): boolean {
  const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
  return this._previousLeft === CLOSED_LEFT_POSITION && gestureStateDx > 0;
},

_swipeFullSpeed(gestureState: Object): void {
  this.state.currentLeft.setValue(this._previousLeft + gestureState.dx);
},

_swipeSlowSpeed(gestureState: Object): void {
  this.state.currentLeft.setValue(
    this._previousLeft + gestureState.dx / SLOW_SPEED_SWIPE_FACTOR,
  );
},

_isSwipingExcessivelyRightFromClosedPosition(gestureState: Object): boolean {
  const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
  return (
    this._isSwipingRightFromClosed(gestureState) &&
    gestureStateDx > RIGHT_SWIPE_THRESHOLD
  );
},

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

Для того чтоб проанализировать производительность нам нужно заглянуть под капот React Native. А там есть две области(realm), которые работают независимо друг от друга: область JavaScript — здесь мы реализуем бизнес логику нашего приложения и UI(родная) область — там где живут элементы управления. Общение между двумя этими областями происходит при помощи моста. И так как для отправки данных через мост нужна сериализация, частое общение выходит дорого.

События касаний — это родные для платформы конструкции и возникают они в UI области. На каждый кадр анимации взаимодействия эти события пересылаются через мост для обработки методом _handlePanResponderMove в JavaScript процессе. Как только бизнес логика вычислит ответ, устанавливается значение Animated Value. И так как обновление элементов происходит в UI области, нам надо переслать данные через мост еще раз.

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

Реализация с использованием нативных инструментов

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

Это означает реализовывать все дважды. Один раз на Objective-C для iOS и еще один на Java для Android. Как правило, легче добиться производительности 60 FPS при написании с использованием родных инструментов потому что мы исключаем передачу данных по мосту, а так же храним и бизнес логику и элементы в UI процессе.

Так как мы открываем практически весь наш нативный код, у нас получилось несколько библиотек типа react-native-swipe-view, которая реализует сдвигаемые карточки и react-native-action-view для действий над строкой списка. Очевидно, что без общего решения каждый новый сценарий использования превращается в такую узкоспециализированную библиотеку.

Основная проблема данного подхода в том, что он требует наличия, как правило, двух программистов с опытом разработки нативных приложений. Для этих целей у нас в Wix есть около 10% frontend разработчиков с навыками Objective-C/Swift или Java.

Но этого недостаточно. Мы должны стремиться к лучшему и найти элегантное решение общего назначения.

Учимся у анимаций

На самом деле у простых анимаций схожие проблемы. Простая реализация изменяла бы свойства объектов от кадра к кадру в JavaScript области. Это бы создавало много "шума на мосту" и привело бы к потере производительности. Но как мы знаем, библиотека Animated стала решением для анимаций со скоростью 60 FPS в React Native. Как она работает?

Основная идея, ставшая основой этой библиотеки — это декларативный интерфейс(declarative API) описания анимаций. Если мы можем описать всю анимацию от начала до конца заранее, тогда все это описание можно сериализовать и отправить через мост всего один раз. А дальше драйвер анимации общего назначения просто исполнит её кадр за кадром.

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

Этот метод уменьшает объём данных передаваемых через мост до данных необходимых для инициализации. И он приводит нас к интересному заключению:

Декларативный интерфейс — это то, что позволит нам пересечь последнюю милю

Это очень мощная концепция. Это именно тот тип библиотек, о которых нам стоит думать. Когда бы мы не упёрлись в предел производительности React Native, это тот способ, который позволит его расширить. Все что нам надо сделать — это найти несколько сценариев использования и создать декларативный интерфейс покрывающий их все. Именно это мы сейчас и сделаем.

Декларативный интерфейс для взаимодействий с пользователем

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

  • Наш API должен быть общего назначения. Для того чтоб это проверить мы должны убедиться что он покрывает все 8 сценариев из наших примеров.
  • Наш API должен быть простым. Описание каждого взаимодействия не должно превышать 3-5 строк кода.

До того как переходить к деталям, я хочу упомянуть об одной очень интересной работе, которая проводится в библиотеке Animated и направлена она на пользовательское взаимодействие. Одно из интересных дополнений — это Animated.ScrollView и оно позволяет проводить интерполяцию свойств области прокрутки, основанных на позиции ScrollView. Еще одна интересная незавершённая разработка от Krzysztof Magiera — это react-native-gesture-handler и он позволяет производить интерполяцию свойств объекта в зависимости от пользовательских жестов.

Наш подход немного отличается от них. Мы возьмем 8 UX сценариев из наших примеров и создадим простейший высокоуровневый API, который сможет описать их все.

Стадия первая: определим API

Проанализировав наши примеры мы увидим, что некоторые объекты могут перемещаться по горизонтали, а некоторые по вертикали. Таким образом указание направление(direction) станет хорошей отправной точкой для нашего API.

Еще одно наблюдение заключается в том, что объекты могут свободно перемещаться во время перетаскивания. Но как только пользователь отпускает их, они, как правило, стремятся и фиксируются в определенной точке(snap). Как, например, боковое меню может быть либо закрытым, либо открытым.

И последнее. Чтоб придать функции фиксации реалистичности, нам надо использовать для анимации кривую подобную пружине. И так как мы не хотим чтоб наша пружина прыгала вечно, нам надо добавить трение(или затухание).

Таким образом, на первой стадии наш API будет иметь следующие свойства:

  • horizontal / vertical
  • snap points
  • friction

Теперь давайте попробуем с использованием нашего API описать первые два сценария из наших примеров. Действия для строки списка(ListView row actions) и сбрасываемые карточки(swipeable cards)

React Native: от простой анимации до интерактивной на скорости 60 FPS - 1

<Interactable.View
  horizontalOnly={true}
  snapPoints={[ {x: 0}, {x: 100} ]}
  friction={0.7}
/>

// swipeable cards
<Interactable.View
  horizontalOnly={true}
  snapPoints={[ {x: -360}, {x: 0}, {x: 360} ]}
  friction={0.6}
/>

Для того чтоб карточки улетали при смахивании мы просто указали точки привязки(snap points) за пределами экрана (-360 и 360 логических пикселей). Обратите внимание, что на данный момент мы используем пиксели для упрощения. Позже мы можем добавить поддержку единиц измерения, которые больше подходят для различных разрешений экрана, таких как проценты.

Для начала неплохо, но дизайн декларативного интерфейса — это только первая половина. Вторая половина — это реализация нативного драйвера. Этим мы сейчас и займемся.

Реализация нативного драйвера: Первая попытка

После того как данные для описания взаимодействия сформированы в JavaScript области, они сериализуются и единожды отправляются в UI область. Наш нативный драйвер общего назначения принимает их и выполняет всё взаимодействие только в нативной области. Больше не будет никаких пересылок через мост для просчета каждого кадра взаимодействия, что позволит добиться плавной анимации на скорости 60 FPS.

Давайте начнем с простой реализации на Objective-C. Мы будем перемещать наш объект используя UIPanGestureRecognizer и когда жест перемещения закончится, мы найдем ближайшую точку привязки и плавно переместим к ней объект используя пружинную кривую анимации.

- (void)handlePan:(UIPanGestureRecognizer *)pan {

CGPoint translation = [pan translationInView:self];
self.center = CGPointMake(self.initialPanCenter.x + translation.x, 
                          self.initialPanCenter.y + translation.y);

if (pan.state == UIGestureRecognizerStateEnded) {
  InteractablePoint *snapPoint = [self findClosestPoint:self.snapTo 
                                  toPoint:self.center];
  if (snapPoint) {
    [UIView animateWithDuration:0.8 
     delay:0 
     usingSpringWithDamping:0.7 
     initialSpringVelocity:0 
     options:nil 
     animations:^{
       self.center = [snapPoint positionWithOrigin:self.origin];
     }
     completion:^(BOOL finished) {}];
   }
 }
}

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

Реализация нативного драйвера: Вторая попытка

Давайте посмотрим на более продвинутые модели для управления взаимодействием. Если вы окунётесь в нативный SDK и посмотрите как Apple советует делать сложные и физически реалистичные взаимодействия, то вы наткнётесь на UIKit Dynamics.

Этот сумасшедший API был представлен в iOS 7. Он использует полнофункциональный физический движок под капотом и позволяет нам оперировать такими физическими свойствами как масса, скорость и применять к объекту различные силы. Физические параметры сцены определяются применяемыми моделями поведения(behavior). Мы можем легко изменить предыдущую реализацию:

if (pan.state == UIGestureRecognizerStateEnded) {
  CGPoint velocity = [pan velocityInView:self.superview];
  InteractablePoint *snapPoint = [self findClosestPoint:self.snapTo 
                                  toPoint:self.center];
  if (snapPoint) {

    // начальная скорость
    UIDynamicItemBehavior *itemBehaviour = [[UIDynamicItemBehavior alloc] 
                                            initWithItems:@[self]];
    [itemBehaviour addLinearVelocity:velocity forItem:self];
    [self.animator addBehavior:itemBehaviour];

    // привязка к точке
    UISnapBehavior *snapBehaviour = [[UISnapBehavior alloc] 
                                     initWithItem:self 
                                     snapToPoint:[snapPoint 
                                                  positionWithOrigin:
                                                  self.origin]];
    snapBehaviour.damping = 0.8f;
    [self.animator addBehavior:snapBehaviour];
  }
}

Это уже ближе, но все еще недостаточно. В использовании UIKit Dynamics есть два больших недостатка. Во-первых, нет возможности использовать его на Android. В Android SDK нет ничего что мы могли бы использовать для замены. И, во-вторых, некоторые возможности, такие как привязка, не дают достаточно контроля. Нет способа указать силу, с которой объект будет зафиксирован.

Реализация нативного драйвера: Третья попытка

Давайте добавим немного сумасшествия. Почему бы не реализовать UIKit Dynamics самостоятельно? В конечном счете, физические силы выражаются относительно простыми математическими уравнениями. Разработка физического движка с нуля не должна быть слишком сложной.

Мы можем адаптировать модели поведения из UIKit Dynamics. Давайте, например, возьмем функцию привязки объекта(snapping). Мы можем реализовать её используя пружину. Но как эта пружина должна себя вести? Настало время вспомнить немного физики.

React Native: от простой анимации до интерактивной на скорости 60 FPS - 2

Не стоит особо беспокоиться из-за математики. Это то, что библиотека будет делать под капотом. Статьи на Википедии по Законам Ньютона и Гука дадут вам всю необходимую информацию.

Нам придется рассчитывать силы и скорости каждый кадр нашей анимации. Для того чтоб этого добиться нам нужен высокоточный таймер, который будет обеспечивать выполнение кода со скоростью 60 кадров в секунду. К счастью, у нас как раз есть нативный API созданный специально для этих целей — CADisplayLink. Собрав все вместе мы получим:

self.displayLink = [CADisplayLink displayLinkWithTarget:self 
                    selector:@selector(displayLinkUpdated)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

- (void)displayLinkUpdated {
  CFTimeInterval deltaTime = 0.0;
  CFTimeInterval currentTime = [self.displayLink timestamp];
  if (self.lastFrameTime > 0.0) deltaTime = currentTime - self.lastFrameTime;
  self.lastFrameTime = currentTime;
  [self animateFrameWithDeltaTime:deltaTime];
}

- (void)executeFrameWithDeltaTime:(CFTimeInterval)deltaTime onObject:(PhysicsObject*)object {
  CGFloat dx = self.target.center.x - self.anchorPoint.x;
  CGFloat ax = (-self.tension * dx) / object.mass;
  CGFloat vx = object.velocity.x + deltaTime * ax;

  CGFloat dy = self.target.center.y - self.anchorPoint.y;
  CGFloat ay = (-self.tension * dy) / object.mass;
  CGFloat vy = object.velocity.y + deltaTime * ay;

  object.velocity = CGPointMake(vx, vy);
}

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

Мы пишем декларативный физический движок для React Native

И это чертовски здорово!

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

Расширяем API новыми свойствами

Наш декларативный интерфейс — это хороший фундамент для дальнейших улучшений, но ему все еще недостает функционала для реализации всех хитрых взаимодействий из 8 UX примеров. Давайте рассмотрим панель уведомлений из iOS. Когда пользователь вытягивает панель с достаточной силой, она отпрыгивает от нижней части экрана.

Мы легко можем добавить поддержку такого поведения к нашему интерфейсу. Ограничим передвижение объекта рамками и добавим отскок от краёв:

React Native: от простой анимации до интерактивной на скорости 60 FPS - 3

// Панель уведомлений теперь имеет границы и отскок
<Interactable.View
  verticalOnly={true}
  snapPoints={[ {y: 50}, {y: 667} ]}
  initialPosition={{y: 50}}
  boundaries={{bottom: 667, bounce: 2}}
  friction={0.7}
/>

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

Мы можем добавить сопротивление к передвижению строки привязав одну из её сторон к краю экрана с помощью постоянной пружины. В отличие от точек привязки, эта пружина будет активна во время перетаскивания.

Тогда нам нужно будет решить еще одну проблему. Строка должна свободно сдвигаться влево(чтоб отобразить кнопки действий) и испытывать сопротивление при движении вправо. Мы можем достичь такого поведения добавив в интерфейс для каждой прилагаемой силы, такой как пружина, опциональную область влияния(influence area).

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

React Native: от простой анимации до интерактивной на скорости 60 FPS - 4

<Interactable.View
  horizontalOnly={true}
  snapPoints={[ {x: 0}, {x: -230} ]}
  springPoints={[ {x: 0, tension: 6000, damping: 0.5, influenceArea: {left: 0}} ]}
  friction={0.7}
/>

Как вы видите, при появлении новых сценариев мы можем просто добавить к нашему интерфейсу обобщенные свойства, которые могут их описать.

Расширяем API взаимодействием с Animated

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

Вы можете видеть это поведение на картинке ниже(кнопки действий обозначены синим):

React Native: от простой анимации до интерактивной на скорости 60 FPS - 5

Так же обратите внимание, что объекты, которые мы хотим анимировать(синие кнопки) отличаются от объекта, с которым взаимодействует пользователь(серая строка списка).

Этот эффект далеко не так прост в реализации так как этапы анимации теперь зависят от горизонтального положения строки, а не от таймера. Тем не менее это все еще анимация где свойства объекта(размер и прозрачность) последовательно изменяются. И у нас уже есть мощный инструмент для анимации свойств — библиотека Animated. Давайте посмотрим как мы можем использовать её для наших целей.

Анимация свойств объектов в Animated осуществляются декларативно, определяя интерполяции с использованием Animated.Value:

this._animValue = new Animated.Value(0);

<Animated.View style={{
  transform: [{
    scale: this._animValue.interpolate({
      inputRange: [-150, 0],
      outputRange: [0.3, 1]
    })
  }]
}}>
  ...
</Animated.View>

Так как анимация зависит от горизонтальной позиции строки, не попробовать ли нам передать эту позицию через Animated.Value? Это позволит нам указывать интерполяции, основанные на позиции объекта взаимодействия, для изменения других объектов, которые напрямую с ним не связаны(например кнопки).

Как бы в таком случае это работало с нашим интерфейсом? Мы можем достичь такого поведения передавая Animated.Value в качестве свойства(animatedValueX):

// Типичный код использующий Animated

this._deltaX = new Animated.Value(0);

<Animated.View style={{
  transform: [{
    scale: this._deltaX.interpolate({
      inputRange: [-150, 0],
      outputRange: [0.3, 1]
    })
  }]
}}>
  ...
</Animated.View>

// Наш улучшенный API

<Interactable.View
  horizontalOnly={true}
  snapPoints={[{x: 0}, {x: -230}]}
  animatedValueX={this._deltaX} 
/>

Нативный драйвер осуществит фактическую передачу под капотом. Добиться этого можно с использованием Animated.events. Новые версии Animated поддерживают управление Animated.events используя нативный драйвер. А это означает, что вся анимация, начиная от передачи позиции до интерполяции и обновления объектов будет проходить в нативной области без необходимости пересылать данные через мост. Это отличные новости если мы хотим выполнения анимации со скоростью 60 FPS.

Расширяем API: Финальные штрихи

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

Нам так же необходима поддержка событий чтоб наш JavaScript код знал когда взаимодействие завершено или когда объект достиг точки привязки. И пока мы все еще здесь, было бы здорово добавить тактильную отдачу. Чтоб телефон мог слегка вибрировать, когда объекты сталкиваются. Эти нюансы добавляют приложению лоска, который так необходим для приятного использования.

Время подводить итоги...

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

<Interactable.View
  snapPoints={[{x: 140}, {x: -140}]}
  gravityPoints={[{x: 0, y: 200, strength: 8000, falloff: 40, damping: 0.5, haptics: true}]}
  dragWithSpring={{tension: 2000, damping: 0.5}}
  onStop={this.onStopInteraction}
  animatedValueX={this._deltaX}
  animatedValueY={this._deltaY}
/>

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

У нас получилась полноценная реализация "chat heads" всего в 7 строк кода!

React Native: от простой анимации до интерактивной на скорости 60 FPS - 6

Реально ли он работает со скоростью 60 кадров в секунду?

Просмотр видео далеко не то же самое что самостоятельное взаимодействие на настоящем устройстве. Заметьте, что даже симулятор не позволяет вам ощутить реального взаимодействия так как может пропускать кадры анимации.

Так выполняется ли это на реальном устройстве со скоростью 60 кадров в секунду? Судите сами. Я реализовал все 8 примеров с использованием того движка, который мы только что создали. Вы можете найти получившееся приложение в Apple App Store или Google Play.

Полная реализация нашего физического движка, нативный драйвер для iOS и Android, а так же демонстрационное приложение вы можете найти на нашем GitHub.

Пересекая последнюю милю

Я надеюсь, что вы вынесли из этого интересного эксперимента нечто большее, чем просто классный способ реализации отличных взаимодействий. Наша цель, как сообщества, определить границы React Native, а затем расширить их.

Когда вы сталкиваетесь с интересной проблемой производительности React Native я призываю вас найти еще несколько сценариев её возникновения и создать простой декларативный интерфейс, который может описать их все. Если проблема с производительностью вытекает из-за частой отправки данных через мост(как правило так и есть), нативный драйвер для вашего интерфейса, возможно, станет отличным решением.

Давайте вместе пересечём последнюю милю.

Автор: northicewind

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js