Git Product home page Git Product logo

Comments (16)

gaearon avatar gaearon commented on March 29, 2024

I know React Native handles animations, but I must be missing it in the source. I see InteractionManager which lets you schedule code to be run after animations are completed. But what is actually applying the animation?

I'm not a React Native expert but I bet animations are supposed to be done with requestAnimationFrame which seems to be implemented. You may then be able to use something like:

https://github.com/chenglou/react-tween-state (stable)
https://github.com/chenglou/react-state-stream (bad perf, unstable, but potentially much more fun)

from react-native.

gaearon avatar gaearon commented on March 29, 2024

I'm pretty sure I read some tweet saying requestAnimationFrame bridges to CADisplayLink so it should work properly.

from react-native.

sahrens avatar sahrens commented on March 29, 2024

This is a very complicated space, and we have several implementations internally that we are trying to unify and nail down.

It's definitely possible to use requestAnimationFrame and do whatever you want, but it takes some TLC to get good perf on slower devices. You can use setNativeProp to directly update specific properties without doing a full setState/render pass as a potential perf optimization. Internally we have an implementation that does this well (and also integrates with continuous gestures) that we would like to release soon.

We also have a system for doing global layout animations. Basically you just setState and update whatever (can be as complex a change as you want, creating new nodes, change flow direction from row to column, etc), and configure the global layout animation before returning control to native. Then the layout system (which does run in native but on a background thread) will compute the new layout, then animate all the resulting changes according to the specified config using standard UIView animation.

On Feb 6, 2015, at 8:38 AM, Dan Abramov [email protected] wrote:

I'm pretty sure I read some tweet saying requestAnimationFrame bridges to CADisplayLink so it should work properly.

β€”
Reply to this email directly or view it on GitHub.

from react-native.

vjeux avatar vjeux commented on March 29, 2024

Animations is a very complex and interesting problem. Here are the different options we've experimented with:

Declarative Animations

The first one is to have an API that starts an animation based on a ref.

this.startAnimation('ref', {
  type: this.AnimationTypes.easeInEaseOut,
  property: this.AnimationProperties.scaleXY,
  duration: 0.3,
  fromValue: [0, 0],
  toValue: [1, 1],
});

Right now we're using Pop to do that, but we should also be able to use CoreAnimation. CoreAnimation has the advantage of being executed in a different process with extremely high priority, whereas pop is in a different thread. However, CA doesn't support springs (unless you compute the keyframes yourself and send them to CA).

This kind of animation is very good for fire and forget, which is used in a lot of places. It is mostly insensible to JS thread stalls.

Gesture Driven Animations

If you want to implement a scroll away header, or an image viewer, you must animate based on touch position rather than time. The trigger is onScroll or onResponderMove and you've got to come up with the position/dimension/opacity of all the elements that are animated based on the position.

In order to apply those changes, you can either do setState, re-render and re-apply the diff algorithm. If you are careful with shouldComponentUpdate it is possible to get it fast enough. Another technique is to use ref.setNativeProps(). This is the equivalent of taking the dom node and modifying the attributes directly. This has almost no overhead but the modifications can be out of sync in the next render if not careful.

In order to come up with the interpolated values, there are two solutions. The first one is to do the math yourself, this works and is fast but the code is quickly impossible to understand and super hard to review. It's easier to factor your code a bit more declaratively by using interpolators.

var ToTheLeft = {
  opacity: {
    from: 1,
    to: 0.7,
    min: 0,
    max: 1,
    type: 'linear',
    extrapolate: false,
    round: 100,
  },
  left: {
    from: 0,
    to: -SCREEN_WIDTH * 0.3,
    min: 0,
    max: 1,
    type: 'linear',
    extrapolate: true,
    round: PixelRatio.get(),
  },
};

This makes it super clear how the animation works, but if implemented naively, is pretty slow. You've got to parse this structure and do dynamic execution based on what attributes there are. What we're doing instead is to pass this structure to a function called buildStyleInterpolator which generates specialized code to do the interpolation via a string that we then eval.

function(result, value) {
  var didChange = false;
  var nextScalarVal;
  var ratio;
  ratio = (value - 0) / 1;
  ratio = ratio > 1 ? 1 : (ratio < 0 ? 0 : ratio);
  nextScalarVal = Math.round(100 * (1 * (1 - ratio) + 0.7 * ratio)) / 100;
  if (!didChange) {
    var prevVal = result.opacity;
    result.opacity = nextScalarVal;
    didChange = didChange  || (nextScalarVal !== prevVal);
  } else {
    result.opacity = nextScalarVal;
  }
  ratio = (value - 0) / 1;
  nextScalarVal = Math.round(2 * (0 * (1 - ratio) + -30 * ratio)) / 2;
  if (!didChange) {
    var prevVal = result.left;
    result.left = nextScalarVal;
    didChange = didChange  || (nextScalarVal !== prevVal);
  } else {
    result.left = nextScalarVal;
  }
  return didChange;
}

This way we get the nice API and the nice perf :) We can also do some cool optimizations such as returning a boolean that tells us if anything changed to avoid sending unchanged values through the bridge.

Usually, once the touch is released, we compute the velocity and continue using a declarative animation.

Work Scheduling

There is no silver bullet for getting smooth 60fps animations, you've got to avoid doing anything else while animating. In React Native, none of the setTimeout and XHR callbacks are being invoked while there is a touch happening. This is a very strict rule that we're probably going to soften in the future.

We're also doing a similar orchestration technique via InteractionManager to make sure that no work is being done when an animation is being executed. Those techniques are very effective, when the animation/touch is running, there's almost no code except for computing top/left/width/height of a few elements that is being executed. Almost no code or allocations (no GC) happen and we get super smooth animations, even though they are in JS.

Yet, we still want to do some work while an interaction is going on, for example you want to display the next page of content that you fetched from the server during an infinite scroll. In order to be able to do that without dropping frames, Relay has been designed to be able to process data in small chunks, instead of freezing the JS thread for 300ms. We're also investigating running Relay data processing part in a separate thread.

Layout driven animations

The last piece that is unique to React Native, is the ability to animate based on layout changes. All the updates during a frame are batched together and we control the layout algorithm. This means that we can log the all the layout updates (top/left/width/height) that will happen and instead of setting them instantly, we can interpolate them over time. The code for it is extremely simple from a developer perspective:

componentWillUpdate: function(props) {
  if (props.isDatePickerShown !== this.props.isDatePickerShown) {
    Animation.Layout.configureNext(Animation.Layout.Presets.easeInOut);
  }
},

It will smoothly move around all the elements being displayed. There's a setting to decide how to interpolate new/deleted elements. This technique is useful for adding animations across the app very quickly.

Conclusion

By using a combination of those three techniques, we've been able to produce high quality, 60fps animations in many places of our apps. The great aspect is that, unlike with the web, we can play with various threading models and move work around to find the best tradeoffs.

There's still a lot of research to do here, for example it would be nice to send gesture driven animations to CoreAnimation for them to be executed on a different process. One idea we had was to run some React components in the main thread (similar to ScrollView being on the main thread but written in JS). The various APIs are still a bit hard to use and need some polish...

from react-native.

vjeux avatar vjeux commented on March 29, 2024

Closing as this is not super actionable. Please keep discussing here if need be :)

from react-native.

jlongster avatar jlongster commented on March 29, 2024

Thanks for all the really detailed comments! I've been busy engaging people from yesterday's post so I haven't had a chance to read through it all yet. I'll probably respond here but it makes sense to close the issue.

from react-native.

jordanna avatar jordanna commented on March 29, 2024

Thank you @vjeux for the fantastic summary! I've been hacking in some pre-baked animations like zoom-in/out using CoreAnimation (applied post-layout) for a small prototype I'm working on. I noticed interface stubs like RCTAnimationConfig while digging in the guts of ReactKit, so this answers what it could be used for :)

Re: Declarative Animations

Are there any plans to make this fire and let me know when animation is done (vs. fire and forget)? I'm thinking either through callbacks that perhaps can be specified as additional arguments in this.startAnimation, or better yet, have it return a promise or something similar.

The primary use case for this is view dismissal animations, i.e. animation needs to finish before component unmounts. I admit I wasn't too keen on using ReactTransitionGroup (for the web project), so I developed a mixin that applied CSS animations in a similar declarative manner and allowed you to specify a callback function:

// zoom and fade out
this.animate({
    target: 'myRef',
    scale: 0.5,
    opacity: 0,
    duration: '250ms',
    callback: function() {
        // unmount
    }
});

This worked well for unmounting alone, but didn't nicely cover other use cases where other components or mixins were interested when the animation ends, e.g. logging. I'm looking to refactor it now with some RxJs, so this.animate returns an Rx Observable.

In any case, I'm going to hack in something similar in my ReactNative prototype for the time being. Definitely looking forward to trying out these animation APIs when they become available!

from react-native.

vjeux avatar vjeux commented on March 29, 2024

Sorry, "fire and forget" was misleading, there's a callback for when the animation is over so that you can chain another one/unmount the component/do some expensive computation

from react-native.

jlongster avatar jlongster commented on March 29, 2024

I've been thinking through this a lot still, and I don't really have a follow-up comment. Just wanted to say thanks again @vjeux for the detailed response. The "Layout driven animations" part was exactly what I was interested in.

from react-native.

skevy avatar skevy commented on March 29, 2024

I just want to chime in here and say @vjeux's explanation of animations is one of the best ways I've ever seen animation code described. +1 and bookmarking this thread for later reference.

from react-native.

NicoleY77 avatar NicoleY77 commented on March 29, 2024

+1

from react-native.

wootwoot1234 avatar wootwoot1234 commented on March 29, 2024

Thanks @vjeux. I'm new to React Native and still trying to wrap my head around everything. I just re-read this thread after playing around with animations and it's very helpful.

I've been using AnimationExperimental, is that the same thing at Pop? If not, is there a good example out there of using Pop in React Native? If AnimationExperimental is an implementation of Pop, do you have an example of using the callback? I would like to chain animations.

from react-native.

brentvatne avatar brentvatne commented on March 29, 2024

@wootwoot1234 - AnimationExperimental is deprecated. It uses CoreAnimation explicit animations under the hood, not Pop :) Unfortunately we're in an awkward limbo phase where a new animation API is about to land, but isn't quite ready yet.. I would recommend looking at react-tween-state or rebound.js (which you can grep for in the react-native source and find it is used in multiple places) to do pure js animations for now.

from react-native.

skevy avatar skevy commented on March 29, 2024

@wootwoot1234 You can also use GSAP to do imperative animations on react
native. Simple demo here: http://github.com/skevy/react-native-gsap-demo
On Fri, Jun 19, 2015 at 2:33 PM Brent Vatne [email protected]
wrote:

@wootwoot1234 https://github.com/wootwoot1234 - AnimationExperimental
is deprecated. It uses CoreAnimation explicit animations under the hood,
not Pop :) Unfortunately we're in an awkward limbo phase where a new
animation API is about to land, but isn't quite ready yet.. I would
recommend looking at react-tween-state or rebound.js (which you can grep
for in the react-native source and find it is used in multiple places) to
do pure js animations for now.

β€”
Reply to this email directly or view it on GitHub
#46 (comment)
.

from react-native.

wootwoot1234 avatar wootwoot1234 commented on March 29, 2024

@brentvatne, you're all over the place! :) You've been really helpful to me today, thanks! I'll check out the js animations and will look forward to the new animations api.

@skevy thanks for the link, I'll check that out too.

from react-native.

keithkml avatar keithkml commented on March 29, 2024

Hello, I read this entire thread and I don't understand the actual resolution.

Sorry for using terms from other bug tracking systems, but was this issue closed as "won't fix," "obsolete," or "invalid"? I'm trying to understand whether animating changes in flex components is possible in RN.

Thanks!

from react-native.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.