Skip to content

Instantly share code, notes, and snippets.

@furf
Last active May 26, 2017 21:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save furf/69755d9d2e8862a5b973dbc30d27595a to your computer and use it in GitHub Desktop.
Save furf/69755d9d2e8862a5b973dbc30d27595a to your computer and use it in GitHub Desktop.
React components for parallax scrolling.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames/bind';
const now = Date.now();
const PERSPECTIVE_KEY = `__perspective--${now}__`;
const DISABLED_KEY = `__perspective--${now}--disabled__`;
/**
* Parallax
*/
export class Parallax extends Component {
static propTypes = {
nodeName: PropTypes.string,
perspective: PropTypes.number,
disabled: PropTypes.bool,
onScroll: PropTypes.func,
};
static defaultProps = {
nodeName: 'div',
perspective: 1,
disabled: false,
onScroll: null,
};
static className = 'parallax';
static defaultStyle = {
height: '100vh',
};
static requiredStyle = {
overflowX: 'hidden',
overflowY: 'auto',
perspectiveOriginX: '100%',
};
handleScroll = event => {
const { scrollTop, scrollHeight, offsetHeight } = event.target;
this.props.onScroll(scrollTop / (scrollHeight - offsetHeight));
}
render() {
const {
nodeName,
perspective,
disabled,
onScroll,
className,
style,
children,
...rest,
} = this.props;
return React.createElement(nodeName, {
className: classnames(className, Parallax.className),
style: {
...Parallax.defaultStyle,
...style,
...Parallax.requiredStyle,
perspective: disabled ? 'none' : `${perspective}px`,
},
onScroll: onScroll ? this.handleScroll : null,
...rest,
}, applyPropsToChildren(children, {
[PERSPECTIVE_KEY]: perspective,
[DISABLED_KEY]: disabled,
}));
}
}
/**
* ParallaxGroup
*/
export class ParallaxGroup extends Component {
static propTypes = {
nodeName: PropTypes.string,
flat: PropTypes.bool,
};
static defaultProps = {
nodeName: 'div',
flat: false,
};
static className = 'parallax__group';
static defaultStyle = {
height: '100%',
};
static requiredStyle = {
position: 'relative',
};
render() {
const {
nodeName,
flat,
className,
style,
children,
[PERSPECTIVE_KEY]: perspective,
[DISABLED_KEY]: disabled,
...rest,
} = this.props;
return React.createElement(nodeName, {
className: classnames(className, ParallaxGroup.className),
style: {
...ParallaxGroup.defaultStyle,
...style,
...ParallaxGroup.requiredStyle,
transformStyle: flat ? 'flat' : 'preserve-3d',
},
...rest,
}, applyPropsToChildren(children, {
[PERSPECTIVE_KEY]: perspective,
[DISABLED_KEY]: disabled,
}));
}
}
/**
* ParallaxLayer
*/
export class ParallaxLayer extends Component {
static propTypes = {
nodeName: PropTypes.string,
depth: PropTypes.number,
};
static defaultProps = {
nodeName: 'div',
depth: 0,
};
static className = 'parallax__layer';
static requiredStyle = {
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
transformOriginX: '100%',
};
render() {
const {
nodeName,
depth,
className,
style,
children,
[PERSPECTIVE_KEY]: perspective,
[DISABLED_KEY]: disabled,
...rest
} = this.props;
const translateZ = -depth;
const scale = (1 - translateZ) / perspective;
return React.createElement(nodeName, {
className: classnames(className, ParallaxLayer.className),
style: {
...style,
...ParallaxLayer.requiredStyle,
transform: disabled ? 'none' : `translateZ(${translateZ}px) scale(${scale})`,
},
...rest,
}, children);
}
}
/**
* ensureArray
*/
function ensureArray(value) {
return Array.isArray(value) ? value : [value];
}
/**
* applyPropsToChildren
*/
function applyPropsToChildren(children, props) {
return ensureArray(children).map(function(child, key) {
if (child.type === ParallaxGroup || child.type === ParallaxLayer) {
return React.cloneElement(child, Object.assign({}, props, { key }));
} else {
return child;
}
});
}
<Parallax nodeName="article" onScroll={this.handleScroll}>
<ParallaxGroup nodeName="section">
<ParallaxLayer depth={5}>
background
</ParallaxLayer>
<ParallaxLayer depth={2}>
middle
</ParallaxLayer>
<ParallaxLayer>
foreground
</ParallaxLayer>
</ParallaxGroup>
<ParallaxGroup nodeName="section" flat={true}>
<ParallaxLayer depth={5}>
background
</ParallaxLayer>
<ParallaxLayer depth={2}>
middle
</ParallaxLayer>
<ParallaxLayer>
foreground
</ParallaxLayer>
</ParallaxGroup>
</Parallax>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment