Skip to content

Instantly share code, notes, and snippets.

@gilbox
Last active August 29, 2015 14:27
Show Gist options
  • Save gilbox/a65b3bb23ef84627346f to your computer and use it in GitHub Desktop.
Save gilbox/a65b3bb23ef84627346f to your computer and use it in GitHub Desktop.
Idea for functional react scroll-based animation
const identity = x => x;
const topTop = containerRect => rect =>
~~(rect.top - containerRect.top);
const translate3dFormatter = value => `translate3d(${value.join('px,')}px)`;
const translate3d = (...args) => ({
value: args,
formatter: translate3dFormatter,
factory: translate3d
})
function tweenValues(progress, a, b) {
if (a.value) {
if (!b.value) throw(Error('tweenValues mismatch: tried to tween wrapped and unwrapped values'));
return a.factory(...tweenValues(progress, a.value, b.value));
} else {
if (a instanceof Array) {
if (!b instanceof Array) throw(Error('tweenValues expected two arrays but only found one'));
return a.map((value,index) => value + progress*(b[index] - value));
} else {
return a + progress * (b-a);
}
}
}
const resolveValue = (value) =>
x.value ? x.formatter(value) : x;
// currently only supports 2 keyframes
const tween = (position, keyframes) => {
const positions = Object.keys(keyframes);
const position0 = positions[0];
const position1 = positions[1];
if (position <= position0) return resolveValue(keyframes[position0]);
if (position >= position1) return resolveValue(keyframes[position1]);
const range = position1 - position0;
const delta = position - position0;
const progress = delta / range;
return resolveValue(tweenValues(progress, keyframes[position0], keyframes[position1]))
}
class DocumentRect extends Component {
static defaultProps = { formulas: [identity] }
constructor(props) {
super(props);
this.state = { rect: null };
}
componentDidMount() {
window.addEventListener('scroll', event => {
this.setState({ rect: document.documentElement.getBoundingClientRect() });
});
}
render() {
const rect = {this.state};
return this.props.children(...this.props.formulas.map(formula => formula(rect)))
}
}
class DivRect extends Component {
static defaultProps = { formulas: [identity] }
constructor(props) {
super(props);
this.state = {rect:null};
}
componentWillReceiveProps() {
const node = React.findDOMNode(this.div);
const rect = node.getBoundingClientRect();
this.setState({rect});
}
render() {
const rect = {this.state};
return <div ref={r => this.div = r} {...this.props}>
{this.props.children(...this.props.formulas.map(formula => formula(rect)))}
</div>;
}
}
const calculateScrollY = ({top}) => -top;
/// render
<DocumentRect formulas={[identity,calculateScrollY]}>
{(documentRect, scrollY) =>
<DivRect formulas={[topTop(documentRect)]}>
{ (posTopTop) =>
<h1
style={{
transform: tween(scrollY, {
[posTopTop]: translate3d(0, 150, 0),
[posTopTop+200]: translate3d(0, 100, 0)
})
}}>Hello</h1>
}</DivRect>
</DocumentRect>
@gilbox
Copy link
Author

gilbox commented Aug 13, 2015

a more sophisticated tween function could handle something like this:

     <h1
      style={tween(scrollY, {
          [posTopTop]: { transform: translate3d(0, 150, 0), background: rgba(255,0,0,1) }
          [posTopTop+200]: { transform: translate3d(0, 100, 0), background: rgba(0,255,0,1) }
        })}>Hello</h1>

rgba would work similar to translate3d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment