Skip welcome & menu and move to editor
Welcome to JS Bin
Load cached copy from
 
<!DOCTYPE html>
<html>
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
<script src="//fb.me/react-with-addons-0.11.0.js"></script>
<script>
  var easingTypes = {
  // t: current time, b: beginning value, c: change in value, d: duration
  // new note: I much prefer specifying the final value rather than the change
  // in value this is what the repo's interpolation plugin api will use. Here,
  // c will stand for final value
  linear: function(t, b, _c, d) {
    var c = _c - b;
    return t*c/d + b;
  },
  easeInQuad: function (t, b, _c, d) {
    var c = _c - b;
    return c*(t/=d)*t + b;
  },
  easeOutQuad: function (t, b, _c, d) {
    var c = _c - b;
    return -c *(t/=d)*(t-2) + b;
  },
  easeOutQuartic: function (t, b, _c, d) {
    var c = _c - b;
    var ts=(t/=d)*t;
    var tc=ts*t;
    return b+c*(-1*ts*ts + 4*tc + -6*ts + 4*t);
  },
  easeInOutQuad: function (t, b, _c, d) {
    var c = _c - b;
    if ((t/=d/2) < 1) return c/2*t*t + b;
    return -c/2 * ((--t)*(t-2) - 1) + b;
  },
  easeInElastic: function (t, b, _c, d) {
    var c = _c - b;
    var s=1.70158;var p=0;var a=c;
    if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
    if (a < Math.abs(c)) { a=c; var s=p/4; }
    else var s = p/(2*Math.PI) * Math.asin (c/a);
    return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
  },
  easeOutElastic: function (t, b, _c, d) {
    var c = _c - b;
    var s=1.70158;var p=0;var a=c;
    if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
    if (a < Math.abs(c)) { a=c; var s=p/4; }
    else var s = p/(2*Math.PI) * Math.asin (c/a);
    return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
  },
  easeInOutElastic: function (t, b, _c, d) {
    var c = _c - b;
    var s=1.70158;var p=0;var a=c;
    if (t==0) return b;  if ((t/=d/2)==2) return b+c;  if (!p) p=d*(.3*1.5);
    if (a < Math.abs(c)) { a=c; var s=p/4; }
    else var s = p/(2*Math.PI) * Math.asin (c/a);
    if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
    return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
  },
  easeInBack: function (t, b, _c, d, s) {
    var c = _c - b;
    if (s == undefined) s = 1.70158;
    return c*(t/=d)*t*((s+1)*t - s) + b;
  },
  easeOutBack: function (t, b, _c, d, s) {
    var c = _c - b;
    if (s == undefined) s = 1.70158;
    return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
  },
  easeInOutBack: function (t, b, _c, d, s) {
    var c = _c - b;
    if (s == undefined) s = 1.70158;
    if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
    return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
  },
  easeInBounce: function (t, b, _c, d) {
    var c = _c - b;
    return c - easingTypes.easeOutBounce (d-t, 0, c, d) + b;
  },
  easeOutBounce: function (t, b, _c, d) {
    var c = _c - b;
    if ((t/=d) < (1/2.75)) {
      return c*(7.5625*t*t) + b;
    } else if (t < (2/2.75)) {
      return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
    } else if (t < (2.5/2.75)) {
      return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
    } else {
      return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
    }
  },
  easeInOutBounce: function (t, b, _c, d) {
    var c = _c - b;
    if (t < d/2) return easingTypes.easeInBounce (t*2, 0, c, d) * .5 + b;
    return easingTypes.easeOutBounce (t*2-d, 0, c, d) * .5 + c*.5 + b;
  }
};
// additive is the new iOS 8 default. In most cases it simulates a physics-
// looking overshoot behavior (especially with easeInOut. You can test that in
// the example
var DEFAULT_STACK_BEHAVIOR = 'ADDITIVE';
var DEFAULT_EASING = easingTypes.easeInOutQuad;
var DEFAULT_DURATION = 300;
var DEFAULT_DELAY = 0;
// see usage below
function returnState(state) {
  return state;
}
var tweenState = {
  easingTypes: easingTypes,
  stackBehavior: {
    ADDITIVE: 'ADDITIVE',
    DESTRUCTIVE: 'DESTRUCTIVE',
  }
};
tweenState.Mixin = {
  getInitialState: function() {
    return {
      tweenQueue: [],
    };
  },
  tweenState: function(a, b, c) {
    // tweenState(stateNameString, config)
    // tweenState(stateRefFunc, stateNameString, config)
    // passing a state name string and retrieving it later from this.state
    // doesn't work for values in deeply nested collections (unless you design
    // the API to be able to parse 'this.state.my.nested[1]', meh). Passing a
    // direct, resolved reference wouldn't work either, since that reference
    // points to the old state rather than the subsequent new ones.
    if (typeof a === 'string') {
      c = b;
      b = a;
      a = returnState;
    }
    this._tweenState(a, b, c);
  },
  _tweenState: function(stateRefFunc, stateName, config) {
    var state = this._pendingState || this.state;
    var stateRef = stateRefFunc(state);
    // see the reasoning for these defaults at the top
    config.stackBehavior = config.stackBehavior || DEFAULT_STACK_BEHAVIOR;
    config.easing = config.easing || DEFAULT_EASING;
    config.duration = config.duration == null ? DEFAULT_DURATION : config.duration;
    config.beginValue = config.beginValue == null ? stateRef[stateName] : config.beginValue;
    config.delay = config.delay == null ? DEFAULT_DELAY : config.delay;
    var newTweenQueue = state.tweenQueue;
    if (config.stackBehavior === tweenState.stackBehavior.DESTRUCTIVE) {
      newTweenQueue = state.tweenQueue.filter(function(item) {
        return item.stateName !== stateName || item.stateRefFunc(state) !== stateRef;
      });
    }
    newTweenQueue.push({
      stateRefFunc: stateRefFunc,
      stateName: stateName,
      config: config,
      initTime: Date.now() + config.delay,
    });
    // tweenState calls setState
    // sorry for mutating. No idea where in the state the value is
    stateRef[stateName] = config.endValue;
    // this will also include the above update
    this.setState({tweenQueue: newTweenQueue});
    if (newTweenQueue.length === 1) {
      this.startRaf();
    }
  },
  getTweeningValue: function(a, b) {
    // see tweenState API
    if (typeof a === 'string') {
      b = a;
      a = returnState;
    }
    return this._getTweeningValue(a, b);
  },
  _getTweeningValue: function(stateRefFunc, stateName) {
    var state = this.state;
    var stateRef = stateRefFunc(state);
    var tweeningValue = stateRef[stateName];
    var now = Date.now();
    for (var i = 0; i < state.tweenQueue.length; i++) {
      var item = state.tweenQueue[i];
      var itemStateRef = item.stateRefFunc(state);
      if (item.stateName !== stateName || itemStateRef !== stateRef) {
        continue;
      }
      var progressTime = now - item.initTime > item.config.duration ?
        item.config.duration :
        Math.max(0, now - item.initTime);
      // `now - item.initTime` can be negative if initTime is scheduled in the
      // future by a delay. In this case we take 0
      var contrib = -item.config.endValue + item.config.easing(
        progressTime,
        item.config.beginValue,
        item.config.endValue,
        item.config.duration
        // TODO: some funcs accept a 5th param
      );
      tweeningValue += contrib;
    }
    return tweeningValue;
  },
  _rafCb: function() {
    var state = this.state;
    if (state.tweenQueue.length === 0) {
      return;
    }
    var now = Date.now();
    state.tweenQueue.forEach(function(item) {
      if (now - item.initTime >= item.config.duration) {
        item.config.onEnd && item.config.onEnd();
      }
    });
    var newTweenQueue = state.tweenQueue.filter(function(item) {
      return now - item.initTime < item.config.duration;
    });
    this.setState({
      tweenQueue: newTweenQueue,
    });
    requestAnimationFrame(this._rafCb);
  },
  startRaf: function() {
    requestAnimationFrame(this._rafCb);
  },
};
</script>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>
</body>
</html>
 
body
  background-color #ecf0f1
.grid
  -webkit-perspective: 1000px
  -moz-perspective: 1000px
  perspective: 1000px
  margin 0 auto
  display flex
  flex-wrap wrap
  padding 5px
.box
  width 150px
  line-height 150px
  flex-grow 1
  float left
  margin 5px
  background #c0392b
  text-align center
  font-size 50px
  font-weight bold
  color #ecf0f1
  font-family arial
  overflow hidden
  
 
/** @jsx React.DOM */
var transitionMixin = {
  initialiseTransitions: function(prop) {
    return this.buildChildren([], this.props[prop])
  },
  updateTransitions: function(prop, nextProps) {
    if (nextProps[prop] !== this.props[prop]) {
      var newData = this.childDiffMerge(this.buildChildren(this.props[prop], nextProps[prop]), this.state.transitionChildren);
      this.throttleChildStates(newData);
    }
  },
  buildChildren: function(oldData, newData) {
    return _.map(_.union(oldData, newData), function(d) {
      return _.merge(d, function(d) {
        var transitionState = 'active', 
            isNew = _.indexOf(newData, d) !== -1, 
            isOld = _.indexOf(oldData, d) !== -1;  
        if (isNew && !isOld) { transitionState = 'entering'; } else
        if (isOld && !isNew) { transitionState = 'leaving'; }
        return { transitionState: transitionState };
      }(d));
    });
  },
  childDiffMerge: function(oldData, newData) {
    return _.merge(oldData, newData, function(a, b) {
      if (a === 'active' && b === 'entering') { return 'active'; } else
      if (a === 'leaving' && b === 'active') { return 'leaving'; } else
      { return undefined }
    });
  },
  throttleTimer: 0,
  childTempArray: [],
  throttledSetState: function() {
    this.setState({ data: this.childTempArray }, function() {
      this.childTempArray = [];    
    });
  },
  throttleChildStates: function(data) {
    clearTimeout(this.throttleTimer);
    this.childDiffMerge(this.childTempArray, data);
    this.throttleTimer = setTimeout(this.throttledSetState, 50);
  },
  mapTransitionChildren: function(component) {
    return _.map(this.state.transitionChildren, function(d, i) {
      return component({
        index: i,
        transitionState: d.transitionState,
        onEnter: this.childEntered,
        onLeave: this.childLeft
      }, null);
    }, this);
  },
  childEntered: function(i) {
    var data = this.state.transitionChildren;
    data[i].transitionState = 'active';
    this.throttleChildStates(data);
  },
  childLeft: function(i) {
    var data = this.state.transitionChildren;
    _.pull(data, data[i]);
    this.throttleChildStates(data);
  }
}
function isVisible (el) {
  var rect = el.getBoundingClientRect();
  return (
    rect.bottom >= 0 &&
    rect.right >= 0 &&
    rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
    rect.left <= (window.innerWidth || document.documentElement.clientWidth)
  );
}
var Box = React.createClass({
  mixins: [tweenState.Mixin],
  
  getInitialState: function() {
    return {
      transition: 0
    };
  },
  componentDidMount: function() {
    if (this.props.transitionState === 'entering') this.fadeIn(this.props.onEnter);
  },
  componentWillReceiveProps: function(nextProps) {
    if (this.props.transitionState === 'active' && nextProps.transitionState === 'leaving') this.fadeOut(this.props.onLeave)
  },
  fadeIn: function(fn) {
    this.tweenState('transition', {
      easing: tweenState.easingTypes.easeInQuad,
      delay: this.props.index * 10,
      duration: 200,
      endValue: 1,
      onEnd: function() {fn(this.props.index)}.bind(this)
    });
  },
  fadeOut: function(fn) {
    this.tweenState('transition', {
      easing: tweenState.easingTypes.easeOutQuad,
      duration: 200,
      endValue: 0,
      onEnd: function() {fn(this.props.index)}.bind(this)
    });
  },
  handleClick: function() {
    this.props.onClick(this.props.index)
  },
  render: function() {
    var transition = this.getTweeningValue('transition');
    var style = {
      transform: 'scale('+ (0.8 + transition * 0.2) +') translateZ(-'+ (1 - transition) * 50 +'px)',
      opacity: transition
    };
    return (
      <div style={ style } className="box" onClick={ this.handleClick }>
        { this.props.label }
      </div>
    );
  }
});
var Grid = React.createClass({
  mixins: [transitionMixin],
  getInitialState: function() {
    return {
      transitionChildren: this.initialiseTransitions('data')
    }
  },
  componentWillReceiveProps: function(nextProps) {
    this.updateTransitions('data', nextProps);
  },
  handleClick: function(i) {
    UpdateModel(i);
  },
  mapChildren: function() {
    return _.map(this.mapTransitionChildren(Box), function(d) {
      return _.merge(d.props, {
        key: d.number,
        label: d.number,
        onClick: this.handleClick
      }(d));
    });
  },
  render: function() {
    return (
      <div className="grid">
        { this.mapChildren() }
      </div>
    );
  }
});
var model = [
  { number: 1 },
  { number: 2 },
  { number: 3 },
  { number: 4 },
  { number: 5 },
  { number: 6 },
  { number: 7 },
  { number: 8 },
  { number: 9 },
  { number: 10 },
  { number: 11 },
  { number: 12 },
  { number: 13 },
  { number: 14 },
  { number: 15 },
  { number: 16 },
  { number: 17 },
  { number: 18 },
  { number: 19 },
  { number: 20 },
  { number: 21 },
  { number: 22 },
  { number: 23 },
  { number: 24 },
  { number: 25 },
  { number: 26 },
  { number: 27 },
  { number: 28 },
  { number: 29 },
  { number: 30 }
];
function UpdateModel(i) {
  model = model.slice();
  model.splice(i, 1);
  React.renderComponent(
    <Grid data={ model } key="grid" />,
    document.body
  );
}
React.renderComponent(
  <Grid data={ model } key="grid" />,
  document.body
);
Output

You can jump to the latest bin by adding /latest to your URL

Dismiss x
public
Bin info
anonymouspro
0viewers