GithubHelp home page GithubHelp logo

react-motion's Introduction

React-Motion

Build Status npm version Bower version react-motion channel on discord

import {Motion, spring} from 'react-motion';
// In your render...
<Motion defaultStyle={{x: 0}} style={{x: spring(10)}}>
  {value => <div>{value.x}</div>}
</Motion>

Animate a counter from 0 to 10. For more advanced usage, see below.

Install

  • Npm: npm install --save react-motion

  • Bower: do not install with bower install react-motion, it won't work. Use bower install --save https://unpkg.com/react-motion/bower.zip. Or in bower.json:

{
  "dependencies": {
    "react-motion": "https://unpkg.com/react-motion/bower.zip"
  }
}

then include as

<script src="bower_components/react-motion/build/react-motion.js"></script>
  • 1998 Script Tag:
<script src="https://unpkg.com/react-motion/build/react-motion.js"></script>
(Module exposed as `ReactMotion`)

Works with React-Native v0.18+.

Demos

Check the wiki for more!

Try the Demos Locally

git clone https://github.com/chenglou/react-motion.git
cd react-motion
npm install
  • With hot reloading (slow, development version): run npm start.
  • Without hot reloading (faster, production version): run npm run build-demos and open the static demos/demo_name/index.html file directly. Don't forget to use production mode when testing your animation's performance!

To build the repo yourself: npm run prepublish.

What does this library try to solve?

My React-Europe talk

For 95% of use-cases of animating components, we don't have to resort to using hard-coded easing curves and duration. Set up a stiffness and damping for your UI element, and let the magic of physics take care of the rest. This way, you don't have to worry about petty situations such as interrupted animation behavior. It also greatly simplifies the API.

This library also provides an alternative, more powerful API for React's TransitionGroup.

API

Exports:

  • spring
  • Motion
  • StaggeredMotion
  • TransitionMotion
  • presets

Here's the well-annotated public Flow type definition file (you don't have to use Flow with React-motion, but the types help document the API below).

P.S. using TypeScript? Here are the React-motion TypeScript definitions!


Helpers

- spring: (val: number, config?: SpringHelperConfig) => OpaqueConfig

Used in conjunction with the components below. Specifies the how to animate to the destination value, e.g. spring(10, {stiffness: 120, damping: 17}) means "animate to value 10, with a spring of stiffness 120 and damping 17".

  • val: the value.

  • config: optional, for further adjustments. Possible fields:

    • stiffness: optional, defaults to 170.
    • damping: optional, defaults to 26.
    • precision: optional, defaults to 0.01. Specifies both the rounding of the interpolated value and the speed (internal).

    It's normal not to feel how stiffness and damping affect your spring; use Spring Parameters Chooser to get a feeling. Usually, you'd just use the list of tasteful stiffness/damping presets below.

- Presets for {stiffness, damping}

Commonly used spring configurations used like so: spring(10, presets.wobbly) or spring(20, {...presets.gentle, precision: 0.1}). See here.


<Motion />

Usage

<Motion defaultStyle={{x: 0}} style={{x: spring(10)}}>
  {interpolatingStyle => <div style={interpolatingStyle} />}
</Motion>

Props

- style: Style

Required. The Style type is an object that maps to either a number or an OpaqueConfig returned by spring() above. Must keep the same keys throughout component's existence. The meaning of the values:

  • an OpaqueConfig returned from spring(x): interpolate to x.
  • a number x: jump to x, do not interpolate.
- defaultStyle?: PlainStyle

Optional. The PlainStyle type maps to numbers. Defaults to an object with the same keys as style above, whose values are the initial numbers you're interpolating on. Note that during subsequent renders, this prop is ignored. The values will interpolate from the current ones to the destination ones (specified by style).

- children: (interpolatedStyle: PlainStyle) => ReactElement

Required function.

  • interpolatedStyle: the interpolated style object passed back to you. E.g. if you gave style={{x: spring(10), y: spring(20)}}, you'll receive as interpolatedStyle, at a certain time, {x: 5.2, y: 12.1}, which you can then apply on your div or something else.

  • Return: must return one React element to render.

- onRest?: () => void

Optional. The callback that fires when the animation comes to a rest.


<StaggeredMotion />

Animates a collection of (fixed length) items whose values depend on each other, creating a natural, springy, "staggering" effect like so. This is preferred over hard-coding a delay for an array of Motions to achieve a similar (but less natural-looking) effect.

Usage

<StaggeredMotion
  defaultStyles={[{h: 0}, {h: 0}, {h: 0}]}
  styles={prevInterpolatedStyles => prevInterpolatedStyles.map((_, i) => {
    return i === 0
      ? {h: spring(100)}
      : {h: spring(prevInterpolatedStyles[i - 1].h)}
  })}>
  {interpolatingStyles =>
    <div>
      {interpolatingStyles.map((style, i) =>
        <div key={i} style={{border: '1px solid', height: style.h}} />)
      }
    </div>
  }
</StaggeredMotion>

Aka "the current spring's destination value is the interpolating value of the previous spring". Imagine a spring dragging another. Physics, it works!

Props

- styles: (previousInterpolatedStyles: ?Array<PlainStyle>) => Array<Style>

Required function. Don't forget the "s"!

  • previousInterpolatedStyles: the previously interpolating (array of) styles (undefined at first render, unless defaultStyles is provided).

  • Return: must return an array of Styles containing the destination values, e.g. [{x: spring(10)}, {x: spring(20)}].

- defaultStyles?: Array<PlainStyle>

Optional. Similar to Motion's defaultStyle, but an array of them.

- children: (interpolatedStyles: Array<PlainStyle>) => ReactElement

Required function. Similar to Motion's children, but accepts the array of interpolated styles instead, e.g. [{x: 5}, {x: 6.4}, {x: 8.1}]

(No onRest for StaggeredMotion because we haven't found a good semantics for it yet. Voice your support in the issues section.)


<TransitionMotion />

Helps you to do mounting and unmounting animation.

Usage

You have items a, b, c, with their respective style configuration, given to TransitionMotion's styles. In its children function, you're passed the three interpolated styles as params; you map over them and produce three components. All is good.

During next render, you give only a and b, indicating that you want c gone, but that you'd like to animate it reaching value 0, before killing it for good.

Fortunately, TransitionMotion has kept c around and still passes it into the children function param. So when you're mapping over these three interpolated styles, you're still producing three components. It'll keep interpolating, while checking c's current value at every frame. Once c reaches the specified 0, TransitionMotion will remove it for good (from the interpolated styles passed to your children function).

This time, when mapping through the two remaining interpolated styles, you'll produce only two components. c is gone for real.

import createReactClass from 'create-react-class';

const Demo = createReactClass({
  getInitialState() {
    return {
      items: [{key: 'a', size: 10}, {key: 'b', size: 20}, {key: 'c', size: 30}],
    };
  },
  componentDidMount() {
    this.setState({
      items: [{key: 'a', size: 10}, {key: 'b', size: 20}], // remove c.
    });
  },
  willLeave() {
    // triggered when c's gone. Keeping c until its width/height reach 0.
    return {width: spring(0), height: spring(0)};
  },
  render() {
    return (
      <TransitionMotion
        willLeave={this.willLeave}
        styles={this.state.items.map(item => ({
          key: item.key,
          style: {width: item.size, height: item.size},
        }))}>
        {interpolatedStyles =>
          // first render: a, b, c. Second: still a, b, c! Only last one's a, b.
          <div>
            {interpolatedStyles.map(config => {
              return <div key={config.key} style={{...config.style, border: '1px solid'}} />
            })}
          </div>
        }
      </TransitionMotion>
    );
  },
});

Props

First, two type definitions to ease the comprehension.

  • TransitionStyle: an object of the format {key: string, data?: any, style: Style}.

    • key: required. The ID that TransitionMotion uses to track which configuration is which across renders, even when things are reordered. Typically reused as the component key when you map over the interpolated styles.

    • data: optional. Anything you'd like to carry along. This is so that when the previous section example's c disappears, you still get to access c's related data, such as the text to display along with it.

    • style: required. The actual starting style configuration, similar to what you provide for Motion's style. Maps keys to either a number or an OpaqueConfig returned by spring().

  • TransitionPlainStyle: similar to above, except the style field's value is of type PlainStyle, aka an object that maps to numbers.

- styles: Array<TransitionStyle> | (previousInterpolatedStyles: ?Array<TransitionPlainStyle>) => Array<TransitionStyle>

Required. Accepts either:

  • an array of TransitionStyle configs, e.g. [{key: 'a', style: {x: spring(0)}}, {key: 'b', style: {x: spring(10)}}].

  • a function similar to StaggeredMotion, taking the previously interpolating styles (undefined at first call, unless defaultStyles is provided), and returning the previously mentioned array of configs. You can do staggered mounting animation with this.

- defaultStyles?: Array<TransitionPlainStyle>

Optional. Similar to the other components' defaultStyle/defaultStyles.

- children: (interpolatedStyles: Array<TransitionPlainStyle>) => ReactElement

Required function. Similar to other two components' children. Receive back an array similar to what you provided for defaultStyles, only that each style object's number value represent the currently interpolating value.

- willLeave?: (styleThatLeft: TransitionStyle) => ?Style

Optional. Defaults to () => null. The magic sauce property.

  • styleThatLeft: the e.g. {key: ..., data: ..., style: ...} object from the styles array, identified by key, that was present during a previous render, and that is now absent, thus triggering the call to willLeave. Note that the style property is exactly what you passed in styles, and is not interpolated. For example, if you passed a spring for x you will receive an object like {x: {stiffness, damping, val, precision}}.

  • Return: null to indicate you want the TransitionStyle gone immediately. A Style object to indicate you want to reach transition to the specified value(s) before killing the TransitionStyle.

- didLeave?: (styleThatLeft: {key: string, data?: any}) => void

Optional. Defaults to () => {}.

  • styleThatLeft: the {key:..., data:...} that was removed after the finished transition.
- willEnter?: (styleThatEntered: TransitionStyle) => PlainStyle

Optional. Defaults to styleThatEntered => stripStyle(styleThatEntered.style). Where stripStyle turns {x: spring(10), y: spring(20)} into {x: 10, y: 20}.

  • styleThatEntered: similar to willLeave's, except the TransitionStyle represents the object whose key value was absent during the last render, and that is now present.

  • Return: a defaultStyle-like PlainStyle configuration, e.g. {x: 0, y: 0}, that serves as the starting values of the animation. Under this light, the default provided means "a style config that has the same starting values as the destination values".

Note that willEnter and defaultStyles serve different purposes. willEnter only triggers when a previously inexistent TransitionStyle inside styles comes into the new render.

(No onRest for TransitionMotion because we haven't found a good semantics for it yet. Voice your support in the issues section.)


FAQ

  • How do I set the duration of my animation?

Hard-coded duration goes against fluid interfaces. If your animation is interrupted mid-way, you'd get a weird completion animation if you hard-coded the time. That being said, in the demo section there's a great Spring Parameters Chooser for you to have a feel of what spring is appropriate, rather than guessing a duration in the dark.

  • How do I unmount the TransitionMotion container itself?

You don't. Unless you put it in another TransitionMotion...

  • How do I do staggering/chained animation where items animate in one after another?

See StaggeredMotion

  • My ref doesn't work in the children function.

React string refs won't work:

<Motion style={...}>{currentValue => <div ref="stuff" />}</Motion>

This is how React works. Here's the callback ref solution.

react-motion's People

Contributors

andarist avatar appsforartists avatar bsansouci avatar chenglou avatar dey-dey avatar gaearon avatar gre avatar heyjohnford avatar holi0317 avatar hzoo avatar iamdustan avatar iclanzan avatar ifndefdeadmau5 avatar istarkov avatar lencioni avatar ludofischer avatar macil avatar martijnrusschen avatar maximalism2 avatar michaeldeboey avatar mirkodrummer avatar nikhilbaradwaj avatar nkbt avatar peterssonjesper avatar souporserious avatar theopak avatar therewillbecode avatar threepointone avatar trysound avatar wyze avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-motion's Issues

Theoretical: Timeline Rewind/Replay Implementation

I think the best way to move animation forward in react is to make it dead simple to experiment with the animation on a set of potentially multipage actions.

For example, let's say I have an admin panel, I have a certain flow to getting from listing to a form and then performing an action and I want to set some nice animations for doing all that, ideally by setting a crude version then experimenting by recording and replaying back.

This can potentially benefit more then just the developer.

For simplicity sake, lets assuming we have the following, as their not that hard (probably) and some technologies related to them are still surfacing as we speak or in their growing stages (eg. baobab v2).

  • a single application state tree like structure (eg. baobab) that holds all the data in the application
  • we save animation configuration exclusively in the tree structure and its inteded to be constant for the lifecycle of the application (except when we're intentionally editing in development)
  • we have a timeline system that can save all changes made to said structure from one predefined start point to a predefined end point
  • we have a simple interface for viewing and editing nodes in the tree (ie. for our purposes mainly animation configuration nodes)
  • we can reset the entire state with out any page reload and we can also save to localStorage and reload

Problems & Questions

How could we go about saving animation state to said structure? (with out key paths everywhere)
How can we go about triggering animation state from said structure?
How can we go about storing animation configuration in the state structure?
How can we go about reading in a human understandable format the configuration from the tree?
How can we go about resetting everything? (ie. stop/replay)
How do we handle animation that's applied to many items? (ie. shared components that appear in multiple areas)

New name!

React-animation is taken on npm. Let's find a better name for it.

Current suggestions:

  • React-Spring (not sure, we might add Decay component later)
  • React-Motion
  • React-Move
  • Reanimation
  • React-Tween

Feeling bored/curious?

This is a very new library. If you were curious as to how its design came to be, you're at the right place!

This issue's focused on the declarative tweening itself. I'll make another one for unmounting animation. For reference, check my React-Europe talk on animation.

I've partitioned the possible APIs into representative categories, then trimmed away the invalid ones to come to what roughly looks like the current state of the API. The APIs are all focused on the physics of a spring.

Note: if there's ever an oversight in my reasoning that results in my trimming a possibly valid API, please do point out!

Note 2: this is a simplified version. Feel free to ask questions on the unclear part.

Here are the specific criteria:

  • Must be able to express dependency between values, e.g. the final value of item1's top should be able to depend on item2's current interpolated value of width. This seems a bit random, but it's needed to do the real physics version of the staggered animation (where one thing animates after another). See the chat head demo1.jsx.
  • Must be able to work with a tree of children, not just a flat list like TransitionGroup.
  • Must be optimizable.
  • Animated value granularity must be on the level of numbers, not components. A component should be able to have different tweening springs/curves for its different style values.

Please also point out if my criteria are too extreme and cover unnecessary use-cases.

1 Put style directly on children

1.1 Directly style on DOM components

1.1.1
<Spring>
  <div style={realStyle}>a</div>
</Spring>

Can't interpolate.

1.1.2
<Spring>
  <div style={{transform: `scale(${scalarValue}px, 0, 0)`}}>a</div>
</Spring>

Means scalarValue has to be in some configuration, in which case the constraint of styling directly on DOM components doesn't apply. Scratched. Generalized version below in point 2.

1.2 Use wrapper component

1.2.1

<Spring>
  <Thing style={top: interpolateValue(destinationValue, interpolationConfig)}
    <div>a</div>
  </Thing>
</Spring>

Thing wrapper provided by Spring, somehow.
It'll interpolate from whatever current value (stored in Thing's state) to the destinationValue.
Unfortunately, can't animate top unless Thing passes props to child div; next point.

1.2.2

<Spring>
  <Thing style={{transform: `scale(${interpolate(10)}px, 0, 0)`}}>b</Thing>
  <Thing style={{transform: {scale: [interpolate(10), 0, 0]}}}>b</Thing>
</Spring>

Compute Thing using HOC.
Supports both flat list and tree children.
How to do dependencies on currently transitioning values? Can't, unless with a dedicated binding API. Let's not do bindings for now?

2 Put style inside a configuration + let user manipulate and generate children

This implies the configuration is stored somewhere inside owner state, callback passed to generate children, context, etc. They're mostly equivalent (?).

2.1 Use wrapper component

<Spring>
  <Thing>
    <div style={configPart}>a</div>
  </Thing>
</Spring>

No need for wrapper. We can directly point to where we want to style.

2.2 No wrapper

2.2.1

<Spring destinationValue={prevTickTops => {a: {top: 1}, b: {top: 2}}}>
  {({a, b}) =>
    <div>
      <div style={a}>a</div>
      <div style={b}>b</div>
    </div>
  )}
</Spring>

Supports both flat list and tree children.
Works with dependencies between final values: generate your data however you want before passing it to destinationValue.
Doesn't work with dependencies between current values (this is the real criteria). You don't know what x's current interpolated value is until you pass the final structure to spring for the interpolation. The chat head demo cheats by using the previous tick's interpolated value (exposed here as prevTickTops, so there's actually a small delay. But maybe this is good enough for most cases?
Can't control granularity. Scratched.

2.2.2 Above, take 2

<Spring destinationValue={prevTickTops => ({
  a: {top: {destValue: 1, springConfig: {stiffness: 10}}},
  b: {top: {destValue: 2, springConfig: {stiffness: 20}}}
})}>
  {({a, b}) =>
    <div>
      <div style={a}>a</div>
      <div style={b}>b</div>
    </div>
  )}
</Spring>

More or less what this library uses. This solves the previous granularity problem.

3 Mixin + state

3.3 tween-state

this.tweenState(['path', 'to', 'state'], config);

"Scratched" in the sense that this is already an established library. Mixins are getting deprecated so this won't be a viable solution in the future. Children functions (as seen above in 2.2.1 and 2.2.2) are a good alternative, which keeps the state inside the wrapper component instead of in the owner's state. The only caveat of keeping the state inside the wrapper is that others can't easily read into that state; you can still do this though:

<Wrapper>
  {interpolatedState => <div onClick={this.handleClick.bind(null, interpolatedState)} />}
</Wrapper>

Which is enough for most cases, from my experience.

Special Hack Key

May be silly but I cant find the "slowmotion", "rave the future" and other keys used in react conf ?

Is it published somewhere ??

Recommended way for applying forces?

Is there a a good way to apply forces to a system? A use case is when you drag an element on a touch device and release it with a specific velocity. You want the element to being thrown into that direction.

Additionally an element could have another force that is being applied on it, so that it will eventually move to a specific end position.

Compare for example the photo gallery of the react-touch demo of Pete Hunt. When you flick with a higher velocity, multiple photos can be navigated at once.

Is there a recommended way for doing this with react-motion?

willLeave 里 console.log('leave') 时发现 requestAnimationFrame 没有被取消

在 demo3 的 RedoMVC 中修改:

willLeave(date, update, destVals, currVals) {
    console.log('leave')
    if (currVals[date].opacity > 0) {
      return update({
        height: 0,
        opacity: 0,
        data: update(currVals[date].data, -1, -1),
      });
    }
  },

在切换 filter 时发现,一旦触发 willLeave 后,不做其他操作,console 将无限打印。

另外,在我个人实现的 todomvc demo中,删除 todo 的方式不是以 height:0px 来表示的,而是 removeChild ,在 react-animation 里暂时没有找到在动画完成后决定是否删除元素的方法。

the case of CSS Transitions

Basically, with the current implementation of Spring, CSS transitions won't work reliably.

There are two reasons that I can see, maybe more, that causes them to not trigger:

1. Will stop rendering after the first frame

If endValue returns an array of ReactElements there will be no interpolation, only the diffing algorithm will be used.
Start state: {counter: 0}
Start of the frame (one step at a time):

  • this.setState({counter: counter + 1})
  • render of the owner
  • Spring loop
  • call to endValue, which returns [ReactElement with key "1"]
  • call to mergeDiff, which return [ReactElement with key "0", ReactElement with key "1"] (because "0" shouldn't leave yet)
  • we check what's new in the mergedObject and call willEnter, which returns ReactElement with key "1" having for className some class used for initial position (and which needs to be transitioned)
  • not interpolation because it's an array of ReactElements
  • Velocity is 0, so this line will be true and the animationLoop will stop

So ReactElement with key "1" had a chance to get mounted, but won't have a chance to see its className change (which is required for the CSS Transition to be triggered.

A possible fix is detailed here

2. FPS too high

That's a bigger problem, which makes me wonder if we should support CSS Transitions.
Basically, if you want something to transition in using CSS transitions, you'll write a willEnter that will return your component with a class with a transition property (say className1). Then you'll rely on the fact that willEnter is called only once when something should be mounted, and then endValue is called, which would set the className to a default one (say className2). This would cause the object to switch from className1 to className2 and the browser would kick in and smoothly transition between the two class's properties (depending what you want to transition).
But because of the nature of how JS handles which function is allowed to run when (in the case of requestAnimationFrame), it's very likely that the Spring loop will run twice in a row, not giving enough time for the browser to see the className change and resulting in no transition.

The only reason why demo5 works right now is because of point number 1. If you comment out this line you'll see this:

css-transition-bug

I want to know what you guys think, if it's worth implementing or not, and if so, what would the best way to go about this be.

My conclusion from this is that to support CSS transitions properly we'd probably need a special Spring and a special animation "loop" (which wouldn't necessarily be an actual loop). I personally think that most things can be handled by JS, and the only place where CSS Transition shine is when you need

Issues related: #72, #77 and #38
ping @brumm

How to animate height , when element heght is not fixed

Such as, We need an collapse component. An collapse has some panel, every panel has a header element.

When header clicked, then panel open or close. The panel open need animate panel heigth for zero to panel's scrollHeight. Close action reverse.

The problem is, I can only get the hight(el.scrollHeight) of panel after the panel render. React-motion require the endState height, before the panel render.

Provide helpers for interpolating colors and svg paths

Right now it's possible (anything is possible when you tween a simple value!), but it'd be nice to expose the helpers that takes an interpolated number and outputs the corresponding color/path/whatever else (suggestion?)

Simplified wrappers for common use cases?

Any plans for creation of simplified wrappers for common transitions (e.g. adding items to list)?
Would be cool to have something similar to the <SimpleTransition /> I'd implemented in this demo, but better 😄

Justifications for the current API's implementation

In conjunction with #9's point 2.2.2, this issue will explain the current API in detail. TransitionSpring (for unmounting) deserves its own issue later. Warning: this is the conceptual API for clarity purpose. Actual implementation might slightly differ.

The overall initial API looked like this:

<Spring destinationValue={{
  a: {top: 1},
  b: {top: 2},
}}>
  {({a, b}) =>
    <div>
      <div style={a}>a</div>
      <div style={b}>b</div>
    </div>
  )}
</Spring>

(This is one of the only few viable solutions I've found that respect the criteria I've listed in #9. Another is @sahrens', which should be 1.2.2. in that issue.)

The object passed to destinationValue is an arbitrary data structure (at least for the normal Spring and not TransitionSpring.

Spring will drill down into the data structure and animate each number, from whatever the current value was, at that position of the collection. This assumes your previous destinationValue and the next one have the same shape. Under the hood, there's also another tree of the same shape: the velocity tree (initialized to all 0 at the beginning). Current position, current velocity, destination position, stiffness and damping together allow you to calculate the next position (which becomes the new current position) and the next velocity.

But it's a bit constraining not able to specify different spring constants (stiffness + damping) for different numbers. Here's the solution we've found:

<Spring destinationValue={{
  a: {top: {value: 1, springConfig: {stiffness: 10, damping: 5}}},
  b: {top: {value: 2, springConfig: {stiffness: 20, damping: 4}}}
}}>
  {({a, b}) =>
    <div>
      <div style={{top: a.top.value}}>a</div>
      <div style={{top: b.top.value}}>b</div>
    </div>
  )}
</Spring>

Now, destValue and springConfig become restricted, special key names for this library. When it drills down the collection, it will skip animating springConfig subcollection (naturally) and animate each destValue according to the springConfig constants provided on the side. For declarativity, the constants carries over to the whole subcollection, until another springConfig overrides it. For convenience, a stiffness or damping of -1 cancels the animation for the subcollection (so overrides the upper level configs).

This gets verbose, so a stiffness of 170 and damping of 26 is set. These two constants gives you a non-bouncing spring (similar to an ease-in curve), which gently decelerates to destination without overshooting. For one, it gets annoying if things bounces all the time. For two, for unmounting, it's easier for users to check the destination's reached with currentPos === destPos instead of currentPos === destPos && currentSpeed === 0.

But the remaining bits still leave things tedious. It would be nice to be able to write:

<Spring destinationValue={{
  a: {top: update(1, 10, 5)},
  b: {top: update(2, 20, 4)},
}}>
  {({a, b}) =>
    <div>
      <div style={a}>a</div>
      <div style={b}>b</div>
    </div>
  )}
</Spring>

This is actually the current API.

What's happening here? The update function is actually nothing but a wrapper:

function update(val, config) {
  return {
    value: val,
    springConfig: config || {stiffness: 170, damping: 26},
  };
}

The received interpolated structure, however, is {a: {top: someValue}, b: {top: someValue}}. The wrapper {val: ..., springConfig: ...} is gone! Internally, Spring received destinationValue and apply the correct interpolation against currentVlaue, stripping away the update wrapper. This gives you the magical feeling of visually marking a collection and letting Spring know what to interpolate, while seemingly receiving a collection with the "same" visual (as in, looking at the code) shape back.

Here's how the update magic fares vs no magic:

  • update
    • pros
      • hides boilerplate, a lot of it.
      • update only makes sense for marking destinationValue. It's not clear why you'd want to expose some wrapping/marking detail to the received interpolated value/velocity (maybe exposed in the future). restricts usage places to dest (not rly if you use it in default val)
    • cons
      • error-prone. Let's say we want to animate one of the [x, y] in a list of [x, y]s: numbers.map(n => someCondition ? [n, n] : update([n, n])). We intentionally gave an impression that the update marker's an invisible thing, but here you've accidentally returned a non-homogeneous collection: let x = update([1, 2, 3]); let values = [...x, 4] // throws. Alternatively, you could have done the less error-prone numbers.map(n => update([n, n], someCondition ? {stiffness: -1, damping: -1} : null) (-1 -1 disables the tweening of values as a handy shorthand). Not sure.
      • kills the assumption that all three trees (current values, current speeds, destination values) are of the same shape. The shape of the latter might vary greatly depending on when you feel like putting the update marker.
  • Expose {val: ..., springConfig: ...} directly
    • pros
      • No magic. In the interpolated values in the children callback, you get back the tree with the above wrapper. The speed tree (when we expose it in the future) also has the same shape.
    • cons
      • boilerplate-y.

We might want to switch to the latter.

Implement some declarative gestures + animation demo

Some crazy idea:

<Flick settings={...}>
  {velocityDelta => 
    <Spring 
      currentVelocity={currentVelocity => velocityDelta === 0 ? currentVelocity :
        increase(currentVelocity, velocityDelta)
      } 
      endValue={...}>
      {currentValues => <div />}
    </Spring>
  }
</Flick>

(Totally unrelated food for thought: what does declarative multitouch gestures look like in idiomatic React?)

Test the perf of CSS keyframes

The idea is to maybe have a constrained API in the future that allows compiling to CSS keyframes instead of using rAF + render. cc @sahrens who'll probably find this relevant.

I've decided to stop theorizing about CSS keyframes' perf vs manipulating DOM + setting style properties vs React + render + setting inline style. Latter will most probably be slower but hey, 1. always need real data to talk about perf, 2. if it's really slower, I want to see how much slower and whether it's worth orienting the API toward the former 2. Relevant post: https://css-tricks.com/myth-busting-css-animations-vs-javascript/

I'll try the perf of React render vs CSS keyframes first. The second method of manipulating DOM and setting style is less worth the trouble of testing, because React-Motion's bottleneck is also there anyway.

I'm actually not sure what'd be an accurate way of testing this. @sahrens did you measure the perf gain from using Core Animation instead of render?

Explore transducerjs/generators for updateCurr*

cc @threepointone who first mentioned generators for avoiding allocating intermediate stuff. Transducerjs can probably do the same.

Generators might give bad perf (constant overhead) though IIRC. But maybe the Regenerator transform that babel uses avoids that? Have to test the real numbers.

Crazy idea for testing

https://twitter.com/_chenglou/status/620462319806447616

Maybe we could try it in this library. It's not user-facing (unless you fail to strip away the tests correctly of course) so I'm fine with experimenting, especially when the repo's still at its infancy. We might not want to switch to something as crazy as this for an established repo.

Alternatively,

@test
assert(...)

And somehow let babel transform & webpack + uglify to strip this away in the same manner.
(I can't believe I'm suggesting decorators)

Naturally, this is mostly testing library-agnostic.

Extract animation logic so that it can be used outside components

The physics around the motion are super useful for other stuff that might not be React related; e.g., animating scroll - which may need to happen after render.
Would it make sense to extract the animation logic into its own module/function so that it can be used outside React's lifecycle? Am I trying to use this in the wrong way altogether? :) Thanks

Add tests for negative number interpolation

If the start value is 0 and the end value is -180, it should properly interpolate in the negatives (and should actually start at 0 up until -180).

Note: this works now, we just need to keep it that way.

can't setState inside the Spring/TransitionSpring

If you do a call to this.setState inside say componentWillReceiveProps of the TransitionSpring, animationLoop won't see the new state. This is because animationLoop caches the state while it's running (see here, it uses subscriber.value, which is a reference to this.state and which will never any setState updates). I think it's something that should be documented somewhere, if we decide to keep the animationLoop to way it is.

The least horrible hack we found is to set a flag this.dirty anywhere to tell the animationLoop to use this.currValue or this.currVelocity instead of the state passed in as argument. I don't like this very much because then the animationStep isn't a pure function, and we lose a lot of the purpose of animationLoop. This might mean that this logic can't be extracted out, and should be inlined inside the component itself.

IE9 Support or Failback

I was just curious if you plan to support IE9 in any capacity? I know the library relies on requestAnimationFrame and that isn't supported in IE9, but I was wondering if it was possible to have any sort of transition-free fallback, for example rather than animating it could just 'snap' to the final position?

Define custom component tag name

Hi,

I use the animation in react on different structure, not all of 'div', such as 'ul' or 'ol', so I want to the coder could be defined the component tag name.

Actually stop rAF-ing when speeds reach zero

Low-hanging fruit. Will write a separate method for traversing the current velocity tree and checking that everything's at zero.

(Future iteration: do transducers help here? A bit tired of reimplementing basic methods for trees).

GitHub Pages for demos

It'd be slick if the demos were available over GH Pages. Then people wouldn't need to clone the repo just to see the demos. =)

Should be as easy as pushing to a gh-pages branch, but I might be overlooking something.

Nice work!

Write actual readme

Fumbled to get this out of in time for the talk. Let's document this correctly and clean up the code.

Sample Usage code does not work as expected.

Not sure exactly what's going on but if you include react-motion.js and then try to use the Sample Usage code you get errors like:

Uncaught ReferenceError: TransitionSpring is not defined

adding in

let TransitionSpring = ReactMotion.TransitionSpring;

Removes that error, but then there is a Syntax error in the render() return jsx, then once that's fixed you get

Uncaught TypeError: Cannot read property 'a' of undefined

Switching between browser tabs

Hi,

Thanks for this awesome lib.

I stumbled upon something suspect when switching between browser tabs while an animation is on.
The following code simply renders an interpolated value (0 at first, then 300 after 1s) and use it in a transformX as well.
Everything works fine.

Until you switch your browser tab, come back to it, switch, come back etc. The value keep changing/bouncing a lot at each switch (due to the low damping it seems? 5 in this example)
Is that expected?

export default class App extends React.Component {
    constructor() {
        super();
        this.state = { value: 0 };
        setTimeout(() => this.setState({ value: 300 }), 1000);
    }
    render() {
        return (
            <Spring endValue={{val: this.state.value, config: [ 100, 5 ] }}>
                {interpolated => <div style={{
                          transform: `translateX(${interpolated.val}px)`,              
                }}>{~~interpolated.val}</div>}
            </Spring>
        );
    }
}

PS: I'm running on Chrome 44 Windows.

Stop allocating so much

Prototyping period is done. Current API somewhat stable. This is still not high-pri (need to get a few other things in, including tests), but time to stop allocating like crazy in core. If we have to we'll turn the codebase inside out to squeeze out that last drop of frame.

Justification for the TransitionSpring API

Aside from a (very) nice-to-have declarative animation API, we need to solve the problem of animation on unmounting. This is a React-specific problem.

TransitionGroup

It has many problems: race conditions, re-mounting while unmounting, lack of control over animation/children tree shape (flat list), etc. TransitionSpring addresses, I think, every single one of the pain points I could come up with from TransitionGroup. I'm not claiming it's a strictly superior solution: for the basic use-cases TransitionGroup can be terser.

TransitionSpring

The normal Spring dictates that your interpolated data structure's shape stays the same from one render to another. In practice, it's very rare to have the shape of your interpolated data change; even if it does, it's probably not a good idea and supporting this use-case complicates this library's API.

The notable exception is when new components appear/disappear/move (since you generated these components through the interpolated data, your data shape naturally changes too). TransitionSpring builds on top of the normal Spring but allows diffing the data structure in a controlled way. Specifically, currently, it asks you to provide an object of key => yourConfig, where yourConfig is the normal data structure you'd use with Spring, with the {val: ..., config: [...]} and all. This means several things:

  • Your components are keyed (you already need a key for your list of React components anyway). This is how TransitionSpring recognizes a missing item and shallow merges in new value and removes old ones (when you instruct it to actually remove it in willLeave by returning null).
  • If your previous render had keys abc, and the next one has cb, and assuming you've asked in willLeave to keep a around for animation, the final, diffed, merged order will be acb. It could have been cba but the current merging algorithm's execution order produces the former. If you really care that much, we can make it pluggable in the future.
  • To use the previous example, that means the merging algorithm gives you this new interpolated structure: {a: ..., c: ..., b: ...}. Since you most likely will iterate on it through Object.keys(myInterpolatedStructure).map for a simple for-in, object key iteration order is crucial. But aren't object keys unordered? Fortunately, they are ordered in JS.
  • Still, why objects and not arrays? Because if it's an array, you'd be forced to provide a key anyways, and your final structure would look so: [{key: 'a', moreStuff: ...}, {key: 'b', ...}]. Which isn't much better than the object format. But yes, it is easier to do order-rearrangement with an array than object. Fortunately, your common use-cases are covered:
    • Insert at the beginning: {newKeyName: myConfig, ...oldObj}.
    • Insert at the end: {...oldObj, newKeyName: myConfig}.
    • Sort/reverse/etc.: lodash and others got you covered. I will also expose a very convenient helper:
Spring.utils.orderKeys(myObj, arrayOfKeysFromMyObj => {
  // manipulate the order
  return manipulatedKeysArray;
});

Which will return you a new object whose keys have manipulatedKeysArray's order =).

There are other considerations I'll skip over there. But since some people might still find arrays to be neater, we can make this work in the future. It's not a conceptual obstacle. Plus, as an alternative, an array of components makes a lot of sense:

<TransitionSpring endValue={[<div key="a"/ >, <div key="b"/>]}>...</TransitionSpring>

Imagine receiving the component itself in your willLeave... Ahhh the possibilities =D.

willEnter/Leave

Intuitive enough? willEnter is called once and passed the newly entered key. willLeave will be called at every tick where key is absent from the new data structure, and ask you to either return null (kill component for real) or that earlier yourConfig part of you data structure so that you can use it to interpolate. Hopefully at this point, the format key => yourConfig makes sense.

The implication of this API are important. For one, you're not bound to rendering a flat list (you can use your data structure in any way). For two, you're not restricted by a timer (unlike TransitionGroup) that warns you when you don't unmount within x seconds (remember what I said in my talk/readme, timer for animation = bad). For three, it enables whole new use-cases for the very concept of transitioning in/out: 8eefb94#diff-7d11bd20d8f177c821173db584397090R29.

Closing thought

I dislike operating on an intermediate data structure that's a step before you rendering the actual components. Ideally, the whole React philosophy is that you do the data "tracking" only at the very end, on components themselves rather than the data that generated them. But for the case of unmounting animation, we have to admit that it triggers a bad-case scenario for React's render paradigm (as TransitionGroup has shown. But someone please prove us wrong), and that we need to go back a level higher to diff on intermediate data. It's not that much more tedious though. I think this library strikes a good balance.

React-motion doesn't work with Radium

Radium's a project for inlining styles with react. When I use the @radium decorator I get the following Javascript error from the Spring's render method.

Uncaught TypeError: this.props.children is not a function

in the following code

render: function render() {
    var currVals = this.state.currVals;

    return _react2['default'].Children.only(this.props.children(currVals));
  }

React-motion works without Radium and Radium works without react-motion. Here's my render method:

render() {
    return (
      <Spring className="box" endValue={{val: this.state.isClosed ? 1 : 0, config: [90, 18]}}>
        {({val}) =>
            <img src={this.props.img}
                 style={[styles.img, this.imgStyle(val)]}
                 onMouseDown={this.handleMouseDown.bind(this)}/>
        }
      </Spring>
    );
  }

Any thoughts?

change the default of willLeave

After writing a couple demos with react-motion, and looking at other people's demos, I realized that there seems to be a pretty common use case for willLeave which is different from the current default.

The current default is willEnter = () => null, meaning that the TransitionSpring will unmount as soon as there's a missing key in the return of endValue. This default makes sense because it's Spring and TransitionSpring interchangeable (if you want to not throw when endValue returns an object of a different shape).

I think TransitionSpring would be mostly used when you do want mounting/unmounting animation. The most common use case seems to be

defaultWillLeave = (key, endValue, currValue, currVelocity) => {
  if(eq(currValue[key], endValue[key]) && noSpeed(currVelocity[key])) { // `eq` is just deep equality
      return null;
    }

   return ??? // This is a problem
}

There are two problems here:

  1. endValue doesn't contain key yet because it's called during the merging, so how to check if you've
    reached something that the function is suppose to return?
  2. What do you return if you haven't reached this ghost endValue[key]?

One possible solution is to make willEnter and willLeave both be allowed to be data structures. If they are data structures we can trivially check if you've reached the destination (because it's the same whatever the key), and then the return would be that data structure.

defaultWillLeave = (key, endValue, currValue, currVelocity) => {
  if(eq(currValue[key], endValue) && noSpeed(currVelocity[key])) {
      return null;
    }

   return endValue
}

This is how you'd use the TransitionSpring

<TransitionSpring endValue={this.endValue} willEnter={{opacity: 0}} willLeave={{opacity: 0}} />

What do you think?

Traditional curve/duration animation

Hi Chenglou,

Here are my thoughts after integrating the Spring API yesterday:

  • Users of my components will have trouble understanding stiffness/damping compared to the slightly more intuitive and common curve/duration.
  • In some instances the Spring animation doesn't return to a stable state.
  • Would be great to see some predefined constants like you get with most curve animation libraries (e.g. stiff_spring, wobbly_spring).

I love the API design but would really like to have the choice to use a Tween object. I know it's got issues like you mentioned in the React-Europe talk but I don't think it would hurt to provide a traditional approach to animation as well.

let Demo = React.createClass({
  getInitialState() {
    return {open: false};
  },

  handleMouseDown() {
    this.setState({open: !this.state.open});
  },

  render() {
    return (
      <div>
        <button onMouseDown={this.handleMouseDown}>Toggle</button>
        <Tween endValue={{val: this.state.open ? 400 : 0, config: [EasingTypes.easeInOutQuad, 2]}}>
          {interpolated =>
            <div className="demo0-block" style={{
              transform: `translate3d(${interpolated.val}px, 0, 0)`,
            }} />
          }
        </Tween>
      </div>
    );
  }
});

Is the goal of react-motion to eliminate any need for CSS transitions?

This library is starting to look really awesome!

I have a few questions about the goals of this library:

  1. Do you see react-motion totally replacing the need for css transitions or is it intended to compliment them?
  2. Should react-motion be used to transition between hover and active states of a component? Are their any performance implications with doing so?
  3. Will we eventually be able to interpolate between color values instead of just integers to transition properties like backgroundColor and borderColor?

Thanks for you great work! Really looking forward to digging into this library in more depth.

Reminder: avoid triggering renders outside of the animation loop

This is just a reminder for the upcoming refactoring/release. We're currently not calling setState anywhere.

We should make sure that nothing is triggering renders other than the animation loop to avoid unnecessary renders. We could either never call setState, or add an extra property to all calls to stateState. Something like this.setState({...state, shouldRender: false}). And have shouldComponentUpdate return newState.shouldRender.

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.