<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
perspective: 1000px
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
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |