chenglou / react-motion

A spring that solves your animation problems.
MIT License
21.68k stars 1.16k forks source link

Is it possible to do this? #271

Open SPAHI4 opened 8 years ago

SPAHI4 commented 8 years ago

Is it possible to do hierarchical timing transitions like this? https://material-design.storage.googleapis.com/publish/material_v_4/material_ext_publish/0B08MbvYZK1iNTGRLb2Zud2RUNFE/animation-meaningfultransitions-hierarchicaltiming-4do_large_xhdpi.webm

appsforartists commented 8 years ago

Can you please include the link to the Material spec that this demonstrates so we can know exactly what you're asking?

StaggeredMotion lets you start animating item n + 1 after item n has crossed a particular threshold. That's probably what you want, but arranging the grid correctly to achieve this effect might be tricky.

threepointone commented 8 years ago

I think it's from here - https://www.google.com/design/spec/animation/meaningful-transitions.html#meaningful-transitions-hierarchical-timing

I made a Delay helper, and wrapped each box with a Delay and Motion. worked out nicely -

import React, {Component} from 'react';
import {render} from 'react-dom';
import {Motion, spring} from 'react-motion';

// helper to delay a prop being passed by `period` ms
class Delay extends Component{
  static defaultProps = {
    period: 0
  };
  state = {
    value: this.props.initial
  };
  refresh(props){
    let {value, period} = props;
    setTimeout(() => this.setState({
      value
    }), period);
  }
  componentDidMount() {
    this.refresh(this.props);
  }
  componentWillReceiveProps(next){
    this.refresh(next);
  }
  render(){
    // function-as-children
    return this.props.children(this.state.value);
  }
}

const boxes = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];

const styles = {
  wrap: {
    width: 500
  },
  box: {
    margin: 5,
    width: 100,
    height: 100,
    float: 'left',
    backgroundColor: 'blue',
    transformOrigin: 'center center'
  }
};

class App extends Component{
  render() {
    return (
      <div style={styles.wrap}>
        {boxes.map((box, i) =>
          <Delay key={box} initial={0} value={1} period={i*80}>{ delayed =>
            <Motion defaultStyle={{scale: 0}} style={{scale: spring(delayed)}}>{ val =>
              <div style={{...styles.box, transform: `scale(${val.scale})`}}>
                {box}
              </div>
            }</Motion>
          }</Delay>)}
      </div>
    );
  }
}

render(<App/>, document.getElementById('app'));
appsforartists commented 8 years ago

Here's your demo as a Pen:

http://codepen.io/anon/pen/eJrKKX?editors=001

StaggeredMotion works well for that same effect. The only potential wrinkle would be if you wanted to have row 2 start transitioning before row 1 is complete; it's doable, just more complicated. (You could nest StaggeredMotion instances, one for the rows and one for the columns, if you knew which item would end up in each.)

With the Delay approach, it seems like bad things would happen if you received new props before the transition had finished. One of the awesome things about ReactMotion (and by extension StaggeredMotion) is that it's reversible and cancelable by default.

threepointone commented 8 years ago

yes agreed! Indeed, there's a class of animations that involve timing etc that isn't really suited for react-motion (at least directly) - these animations usually have their own state/sequencing logic. Fun to solve these on a case by case basis, but there's clearly a need for higher level constructs. I'm hoping the incoming atRest api makes these easier.

CaptainN commented 8 years ago

Any way to get this to work with TransitionMotion? I have a slide system where some slides use React Motion internally, and others use a standard Transition (and mount/unmount with TransitionMotion), all driven by React Router. I'd like to simply chain from the slide's internal animation (if it has one) to the next slide (or use a default if it has none). I have everything working, except I need to delay the new slide's in transition until the parent slide's out is complete (and then in reverse the other way).

It doesn't look like there's a way to do that without an atRest API, but I'm hoping I'm wrong?

CaptainN commented 8 years ago

I ended up delaying the React Router change, which triggers the second animation to give the first a chance to finish. It's not the prettiest, but it'll work for now. :-)

threepointone commented 8 years ago

You could use setTimeout if you know the approximate duration of the animation to fake atRest

devlee commented 7 years ago

Here is my demo as a Pen , you can change the martix and the custom variables to view different effects.

souporserious commented 7 years ago

Awesome work @devlee!!! That's super cool. I might have to try and get that in React Motion UI Pack :)

devlee commented 7 years ago

@souporserious :) I used to make a mistake and now I have used scale instead of changing width and height ~

finnfiddle commented 7 years ago

thanks for the example! however, I havent tested it but shouldn't you cancel the timeout if the component is going to unmount. Something like:

class Delay extends Component{
  static defaultProps = {
    period: 0
  };
  state = {
    value: this.props.initial
  };
  refresh(props){
    let {value, period} = props;
    this.timeout = setTimeout(() => this.setState({
      value
    }), period);
  }
  componentDidMount() {
    this.refresh(this.props);
  }
  componentWillReceiveProps(next){
    this.refresh(next);
  }
  componentWillUnmount(){
    clearTimeout(this.timeout);
  }
  render(){
    // function-as-children
    return this.props.children(this.state.value);
  }
}

That way if it unmount midway through an animation you wont have problems.

hupeky commented 6 years ago

Hi guys, I read this thread before I wrote a demo piece for my not yet finished portfolio site.

TL:DR

GSAP, pre computed delay info, here's the demo:

http://kye.tech/pixelsv5

Background

I was thinking a lot about how to solve this grid motion problem, and this thread was a good starting point for some inspiration.

I did some tests around using react-motion for a matrix of spring like behaviour (water ripple effect), but the performance and serial nature of the spring connections didn't lend itself well to the problem I was looking to solve, in the end I pre-computed a lookup table of delay data and used that with GSAP for it to run arbitrary motions that propagate through the tiles. you can have any number of waves running concurrently , (Click mutliple tiles)

Each time you click a tile, Its sent an animation with a specific delay based on distance from other tiles, wave shape / parameters. As you click more times, each tile stacks its animation, and adds them together, enabling waves that mix.

I used GSAPs custom bezier drawing function to draw my own set of wave shapes (four in total) which meant I could achieve the exact motion styles I was after.

For most purposes this is overkill, but the concept can be used in many different ways,

I built an interface using React to control the motion and colour of the tiles. The tiles are rendered with WebVR / Aframe. You can get some pretty crazy results by adjusting the settings.

Im pulling the inidividual RGBs out of PNGs and pushing them to the tiles.

I built a colour animation system by way of a json object that acts like a script telling GSAP how to animate the colours on the fly. Lending itself to the possibility of slide shows and sprite animations. See sonic !

Its very heavy on computation / memory, 32 x 32 matrix with potentially 5 / 6 separate motions running through them concurrently. is 6000 GSAP animations. lol.

Hope you like. And thanks for inspiration.

http://kye.tech/pixelsv5